VS Project Templates are Aggravating

I love Visual Studio, but some of the work I have to do to make things work the way I want is aggravating.  I just started a new WPF solution to follow along with a chapter in the book, and decided to give it a descriptive name so the directories would look all nice: "Chapter 8 – Styles".  Visual Studio assumes you want to use the solution name as the default namespace for all projects (unless you specify different project and solution names; then it uses the project name).  Of course, the namespace naming rules are more strict than file naming rules, so I end up with the lovely namespace name "Chapter_8___Styles".  Lame.

What’s worse is even if I go into project settings and change this, it only affects new files added to the project, so I have to go through the solution and change every namespace reference.  For the WPF application project template, that’s changes to 3 files before I can build and run.  It sure would be nice if I had an easy way to change this.

"Out of Memory" Errors with GDI+ TextureBrush

The TextureBrush class lets you draw a shape using an image as a texture, giving you some control over the way the texture is tiled.  I encountered a forum post where a user was trying to use TextureBrush to draw sprites to the screen, but he was upset because as he moved the rectangle around it wasn’t moving the texture; the result was similar to having a tiled background covered up and viewed through a small window, then moving the window around.

I looked at the documentation, and saw constructors that let you specify a bounding rectangle.  I misinterpreted what the bounding rectangle did, and thought it specified where the top-left corner of the tiling should start.  The odd thing was introducing this rectangle led to a generic GDI exception with the message "Out of memory."  I found this curious.  At this point I realized that for the problem at hand, there was no reason to use a TextureBrush as opposed to Graphics.DrawImage, so I suggested this course of action.  Of course, I was curious why there was an out of memory exception, so I dug deeper.

Google was no real help; there were basically two forum threads about the problem, and none had a resolution (one suggested a possible accidental recursive paint handler, but that’s ridiculous since the second message wouldn’t be processed until the first completes and it had nothing to do with this case.)  I was about to turn to the MSDN forums when I dug about in the Connect area for any bug reports concerning this.  I found my answer, but it’s somewhat unsatisfactory.

Consider the example image below; it’s a 50×40 image lifted from the VS 2008 image library that represents 2 16×16 images:

Back_Forward

We can use a TextureBrush to draw rectangles using this image; by default the result is a silly tiling of back/forward buttons, and if we move the rectangle around it will look as if we’re sliding a window over a fixed pattern, as described above.  If we specify a bounding rectangle, only the part of the image within the bounding rectangle will be used as the tile.  For example, the following paint handler would tile only the left-facing arrow from the above image:

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        Dim g As Graphics = e.Graphics
        Dim boundRect As Rectangle = New Rectangle(7, 12, 15, 15)
        Dim brush As New TextureBrush(drawImage, boundRect)
        g.FillRectangle(brush, Me.ClientRectangle)
    End Sub

If the bounding rectangle that is specified is outside the bounds of the image that is to be tiled, you get the "Out of Memory" exception.  I don’t understand why there’s no error checking done; it would be trivial to check whether dstRect is outside of the bounds of image, but for whatever reason this was left out of the method and instead you get a cryptic error message that doesn’t really tell you what is wrong.

I hope this ends up on Google and anyone who has the problem in the future finds it helpful.

To demonstrate using TextureBrush in the manner the user wanted, the application below animates a left or right arrow back and forth across the form.  I don’t believe this is the best way to do this, and I’m almost certain that one of the Graphics.DrawImage overloads would be more appropriate, but there could be a use for this I have not forseen:

Public Class Form1

    Dim drawImage As Image
    Dim boundRect As Rectangle
    Dim leftPoint As New Point(7, 12)
    Dim rightPoint As New Point(28, 12)
    Dim tileSize As New Size(15, 15)
    Dim viewRect As New Rectangle(New Point(0, 0), tileSize)
    Dim goingLeft As Boolean = False

    Public Sub New()

        ' This call is required by the Windows Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.

        drawImage = Image.FromFile("Back_Forward.png")

        Dim timer As New Timer()
        timer.Interval = 100
        AddHandler timer.Tick, AddressOf TimerOnTick
        timer.Start()

        boundRect = New Rectangle(rightPoint, tileSize)
    End Sub

    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
        Dim g As Graphics = e.Graphics

        Dim brush As New TextureBrush(drawImage, boundRect)
        brush.TranslateTransform(viewRect.X, viewRect.Y)
        g.FillRectangle(brush, viewRect)
    End Sub

    Private Sub TimerOnTick(ByVal sender As Object, ByVal e As EventArgs)
        Dim increment As Integer

        If goingLeft Then
            increment = -10
        Else
            increment = 10
        End If

        viewRect.X += increment

        If viewRect.X <= 0 Then
            goingLeft = False
            boundRect = New Rectangle(rightPoint, tileSize)
        ElseIf viewRect.X + viewRect.Width >= Me.ClientRectangle.Width Then
            goingLeft = True
            boundRect = New Rectangle(leftPoint, tileSize)
        End If

        Me.Invalidate()
    End Sub

End Class

Managed DirectX Documentation is Installed in VS 2005 Help

I’m working on some research that involves managed DirectX, and after I read a few chapters of the book Managed DirectX 9 Graphics and Game Programming (which I really recommend — more later) I wanted to look at the documentation for some of the classes that were discussed to get a more in-depth understanding. Try as I might, I couldn’t find the documentation, so I turned to Google for answers. The only reasonably decent hits I found were forum posts dated in 2005 discussing how it was still beta and there was no documentation. Useless. I had almost completed an email to Tom Miller, the author of the book, when I noticed the sentence, “I have looked in the VS 2008, VS 2005, and Windows SDK help…” was false. I had looked in VS 2008 and Windows SDK, but not VS 2005.

So, I’m continuing my series of posts inspired by things I tried to use Google to find but failed. If you want to know where the Managed DirectX documentation is in the March 2008 DirectX SDK, look in the Visual Studio 2005 help.

It’s possible I overlooked an installer option but I’m too lazy to check again.

Making a control that accepts arrow keys

I’m working on a project with a custom control on which I want to implement the arrow keys.  I was completely mystified as to why I wasn’t getting KeyUp events when I pressed the arrow keys. I started using Reflector to hunt for where the event was raised in controls I was familiar with, and I stumbled upon the Control.IsInputKey method.  What does this method do? The remarks told me what I needed to know.

Call the IsInputKey method to determine whether the key specified by the keyData parameter is an input key that the control wants. This method is called during window message preprocessing to determine whether the specified input key should be preprocessed or sent directly to the control. If IsInputKey returns true, the specified key is sent directly to the control. If IsInputKey returns false, the specified key is preprocessed and only sent to the control if it is not consumed by the preprocessing phase. Keys that are preprocessed include the TAB, RETURN, ESCAPE, and the UP ARROW, DOWN ARROW, LEFT ARROW, and RIGHT ARROW keys.

I had no idea this existed. It turns out to get events from these keys in any control, you have to override this method and return true. Here’s an example control in C# 3.0 to demonstrate (conversion to C# 2.0 is trivial; just make a backing field for the property). When it has focus (you’ll know because it is white instead of gray), if you push an arrow key the text name of the key you pressed is displayed. If you press any other key, you’ll get “Unknown”. The documentation does seem a little off, as I’m not doing anything special for Esc or Return but they still cause the events to be raised. Still, if you set CustomInput to false, you won’t ever get an arrow key message displayed (the arrow keys seem to move the focus).

using System;
using System.Windows.Forms;
using System.Drawing;

namespace TestIsInput
{
    public class InputField : Control
    {
        public InputField()
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.SetStyle(ControlStyles.Selectable, true);
        }

        public bool CustomInput
        {
            get;
            set;
        }

        protected override bool IsInputKey(Keys keyData)
        {
            if (CustomInput)
            {
                bool isInput = false;

                switch (keyData)
                {
                    case Keys.Up:
                    case Keys.Down:
                    case Keys.Left:
                    case Keys.Right:
                        isInput = true;
                        break;
                    default:
                        isInput = false;
                        break;
                }
                return isInput;
            }
            else
            {
                return base.IsInputKey(keyData);
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            Brush backBrush = this.Focused ? Brushes.White : Brushes.LightGray;

            e.Graphics.FillRectangle(backBrush, this.ClientRectangle);
            ControlPaint.DrawBorder3D(e.Graphics, this.ClientRectangle, Border3DStyle.Sunken);
            StringFormat f = new StringFormat(StringFormatFlags.NoWrap);
            f.Alignment = StringAlignment.Center;
            f.LineAlignment = StringAlignment.Center;

            e.Graphics.DrawString(this.Text, this.Font, Brushes.Black, (RectangleF)this.ClientRectangle, f);
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);

            switch(e.KeyCode)
            {
                case Keys.Up :
                    this.Text = "Up";
                    break;
                case Keys.Down :
                    this.Text = "Down";
                    break;
                case Keys.Left :
                    this.Text = "Left";
                    break;
                case Keys.Right :
                    this.Text = "Right";
                    break;
                default :
                    this.Text = "Unknown";
                    break;
            }

            this.Invalidate();
        }

        protected override void OnEnter(EventArgs e)
        {
            base.OnEnter(e);
            this.Invalidate();
        }

        protected override void OnLeave(EventArgs e)
        {
            base.OnLeave(e);
            this.Invalidate();
        }
    }
}

So, if you need to accept input from one of those keys, keep in mind that you need to override IsInputKey. This was completely new to me!

Some Consistency, Please

Let’s look at the signatures for constructors of ArgumentNullException and ArgumentException:

public ArgumentException(
	string message,
	string paramName
)
public ArgumentNullException(
	string paramName,
	string message
)

Notice how they both take two string parameters, but each has opposite order. This drives me bonkers and I can never get the right one. I kind of wish MS would make a breaking change and fix it one way or the other.

Mixing numbers and replacement strings

This is yet another thing that I found took me too long to find on Google. I’m using the .NET regular expressions, and looking to replace values in a string with numbers, and I need to use the special $1 symbols to keep some of my capture groups around.

So, suppose I have the text:
something.2005.txt

The pattern:
(.*)2005(.*)

And the replacment string:
$12008$2

This produces the undesirable result $12008.txt. Why? Well, the $1 has to be escaped in some way. Whatever it is that scans for variables is obviously interpreting this as $12008 instead of $1; either that’s invalid or the group doesn’t exist so it just uses the text. Personally I think I should be getting “2008.txt” in this case, as a raw $ should take $$ to create, but I won’t complain.

The solution? Surround the number of the group index in brackets:
${1}2008$2

Having trouble with Form.AcceptButton?

Recent explorations of WPF have caused me to revisit things I used to avoid in .NET. I’ve spent a lot of time over the past week or so working with the TableLayoutPanel and FlowLayoutPanel, and I never really realized how much I dislike the designer for some nitpicky alignment things. I made a relatively complicated application to test myself, and though there’s some lingering issues in Windows Forms 2.0 it looks like WPF solves most of them.

The big thing that has been bugging me is I made a dialog to edit program settings, and when I added the “OK” and “Cancel” buttons I set the form’s AcceptButton and CancelButton properties appropriately. However, when I tried the form out, I found that the cancel button worked perfectly but the OK button did nothing. I decided that perhaps I had left something out of my layout logic, so I started a new project and used the designer to set up a simple dialog with a text box and the two buttons; this dialog worked so I figured I’d done something wrong. I decided I’d just make the button’s click event set DialogResult and left the issue alone.

Then today, someone on the VB .NET forum I frequent posted a problem with the AcceptButton property that was exactly opposite of mine: they didn’t want the button to respond to the Enter key or close the dialog. I decided that the answer to his question was probably pertinent to mine, so I revisited the example. This time, I managed to do something in the designer that caused the OK button to not work properly. I was mystified, so I asked the guy on the forums to post some of his code so I could try to figure out what was different. The answer turns out to be a combination of the Button class’s DialogResult property and an oversight on the part of Microsoft.

I wrote a test program to examine the DialogResult property before and after assigning buttons to a form’s AcceptButton and CancelButton properties. CancelButton worked exactly as I expected and changed the button’s DialogResult property to DialogResult.Cancel. However, AcceptButton did not alter the button’s DialogResult property at all. I double-checked this in Reflector and found that the property set method for AcceptButton doesn’t do anything at all with the button’s DialogResult property.

So if you are having trouble with getting the OK button of a dialog to work, and the only step you are taking is to set the form’s AcceptButton property, be sure to always manually set the button’s DialogResult property. AcceptButton‘s property set method does call UpdateDefaultButton, a protected method of Form with no default implementation. It’s possible you could override this method and add the line:

this.AcceptButton.DialogResult = DialogResult.OK

However, since it’s a protected method that is used by the default implementations of the Form class it’s probably a bad idea to assume this will always work. In particular, if they swap the order of the lines of code it will fail. Perhaps you could override the AcceptButton property and call the base implementation, but that’s still kind of icky. I have not tested this in .NET versions later than 2.0, but I doubt it has been fixed.