Archive

Archive for the ‘WPF’ Category

Use the Right Container for ListBox Scrolling

December 15, 2009 4 comments

I learned something interesting about ListBox and scrolling content this week.  In hindsight it is obvious, but trust me: that’s the last thing you want to hear after two hours of beating your head on your keyboard trying to figure out why the stinking ListBox isn’t scrolling.  Read on for the gory details.

The Obvious

What I learned is this: if you have an Auto sized ListBox, meaning that it will stretch its Width and Height to match its contents, the ListBox will never produce a Scrollbar.  Even if the content stretches far off the window: in that case, it just so happens that the ListBox will also stretch far off the window.  Since the content is not larger than the ListBox itself, no Scroll bars.

Obvious, right?  Not so: I have recently had several frustrating experiences with the Auto settings for Width and Height.  Don’t get me wrong! I use them a lot, and I tell others to do the same because they greatly assist in laying out elements, especially in Grids.  The problem comes when they don’t do what I *think* they are going to do.  In the case of the ListBox, I was operating under the assumption that they would Stretch the ListBox to fit the available space in the container, and that is not how they behave.

The Solution

Realizing my error, I understand now that the scrolling behavior is controlled by the relationship of the size of the Content to the defined (as opposed to visible)size of the ListBox itself.  In my case, I had the ListBox as the last element of a StackPanel.  I needed the ListBox to fill the remaining space, and I needed the Content to scroll vertically. 

The Width of the ListBoxItems was set to the width of the ListBox, minus a little to allow for the Scrollbar, so that wasn’t an issue.  I wanted the Height to fill the remaining space, but I had no way to bind to the measurement and I wanted to avoid a lot of code behind hijinx.  As discussed above, setting it to Auto would actually prevent the desired behavior, so that option was out. 

In this particular case, the application is a fixed size, so I was able to set the size of the ListBox manually to fill the remaining space in the StackPanel, and lo and behold my Scrollbar appeared.  All was well, or so I thought…

The Case of the Changing StackPanel

In testing, we realized that the element above the ListBox (an Image control) should only be visible when it had an Image to show.  So naturally I wired up a ValueConverter and bound the Visibility to a boolean property in the ViewModel reflecting whether or not there was an Image to display.

This worked great for the Image, but an ugly thing occurred with the ListBox.  Since its Height was hardcoded, it shifted up in the StackPanel and no longer stretched to the bottom.  It look pretty strange just hanging there, Scrollbar happily in view, but a bunch of empty space beneath it.

спални комплекти

So just when I thought I had it licked, I’m back in the same boat with the same problem: I need my ListBox to automatically fill all the remaining space in the StackPanel.  Wait, what was that?  I want a child to fill all remaining space?  That sounds familiar…

Enter the DockPanel

Once I got back up from having the ton of bricks fall on my head, I realized that I was using the wrong container!  What I really needed in that scenario was a DockPanel.  I was just using StackPanel out of habit, I had no specific need for it, and the items within it would never be more than the Image and the ListBox.

So using Blend’s trusty “Change Layout Type” feature, I changed my StackPanel to a DockPanel.  I made sure the LastChildFill property was checked and docked the Image control to the Top and set the VerticalAlignment to Top.  I then did the same thing for the ListBox and set its Height property to Auto.  Running the application gave me exactly the result I was after in the first place: an automatically resizing ListBox restrained to it’s Parent container’s remaining available space.

Lesson Learned

So aside from learning a lesson about ListBox and scrolling, I also learned that I need to probably pay a little more attention to the container type I am using.  Grid and StackPanel have always been my defaults since they cover most scenarios, but I also need to remember we have other options.

Categories: WPF

Using Custom Validation Rules in WPF

October 13, 2009 7 comments

Anyone who has ever developed data driven applications of any sort has had to deal with validation.  Let’s face it, users make mistakes, even if they are developers!  Validation can be a real pain to implement, and contrary to all our HelloWorld style applications, MessageBox.Show is not the best way to inform our users that we have an issue with their keyboarding skills.

I remember being ecstatic when I learned about the WinForms ErrorProvider. This handy little approach finally gave us a nice way to alert the user of the problem without being too heavy handed in the UI.  Where it failed, though, was in customization.  First of all, it took a lot of code behind to manage the ErrorProvider: checking field values against business rules, updating the provider’s list of errors, or clearing it out when the status was OK ended up being a lot of code, especially when there were a lot of data fields. And then, beyond deciding whether or not the Error symbol would blink (I always hated the blinking exclamation point), there wasn’t a lot of pizzazz we could add.  Of course, that’s all changed with WPF.

Getting Started

Before I go any further, you can find most of the basics in these articles:

I recommend reading these articles. The CodeProject article even links to a Project with a WPF version of ErrorProvider.  I will be using a couple code samples from these articles.

I’m not going to harp too much on the details presented in these other articles.  Instead, I’m going to layout what I did to get this working and hopefully give you enough information to get started.

Using IDataErrorInfo

When you first start looking into validating data in WPF you will no doubt run into IDataErrorInfo.  This seems to be the jumping off point.  Implementing the interface is pretty straightforward.  Here is a typical example:

using System.ComponentModel;
namespace ValidationRulesPlay.ViewModels
{
    public class DataErrorInfoSample : ViewModelBase, IDataErrorInfo
    {
        private int _int1;
        private int _int2;

        public int Int1
        {
            get { return _int1; }
            set { _int1 = value; FirePropertyChangedEvent("Int1"); }
        }

        public int Int2
        {
            get { return _int2; }
            set { _int2 = value; FirePropertyChangedEvent("Int2"); }
        }

        #region IDataErrorInfo Members

        public string Error
        {
            get { return null; }
        }

        public string this[string columnName]
        {
            get
            {
                string result = null;

                switch (columnName)
                {
                    case "Int1":
                        if (Int1 > 9999999)
                            result = "Int1 number cannot be greater than 9999999.";
                        break;

                    case "Int2":
                        if (Int2 > 99)
                            result = "Int2 number cannot be greater than 99.";
                        break;


                    default:
                        break;
                }

                return result;
            }
        }

        #endregion
    }

}

[NOTE: the class above inherits from ViewModelBase, a class I use to implement INotifyPropertyChanged.]

And then you add a piece to your Binding syntax in XAML to activate it:

<TextBox
    x:Name="Int1TextBox"
    ValidatesOnDataError="True"
    VerticalAlignment="Center"
    HorizontalAlignment="Center"
    MinWidth="50"
    Margin="0,0,6,0"/>

This works and will by default wrap the offending textbox with a red border.  It’s not a bad experience for simple examples, but as I see it there are a couple of drawbacks.

First, IDataErrorInfo validated the data after the property has been set. So if I type in a value that is out of range, the target property is still invalidly set with the bad value.  This means that I have to do extra work to correct the error.  In other words, this approach lets me put bad data in that I must then take out.  Wouldn’t it be better if it just didn’t go in in the first place?  Second, this interface uses a custom indexer, which frankly just feels like magic.  It’s a personal preference, and a fear of the unknown, but I just don’t like it.

My other real issue with it is that it ends up being a lot of repetitive code.  Look at the example above and you’ll see two integer values that are both tested to be within a certain range.  While the ranges are different, the action is the same.  Of course, you could create a method, or better yet an Extension Method, to handle this situation.  Maybe it’s because of the hard-coded nature of the validation messages, but it still doesn’t feel right to me.

Creating Custom Validation Rules

Wouldn’t it be better to have a reusable piece of code to handle these similar situations?  One with no real hard coding?  In .NET 3.5 SP1 we have just such an animal, the Custom Validation Rule.

ValidationRule is an abstract class that we can inherit from to create a custom validation rule class of our own.  I could certainly create a custom rule class for each property in my DAL, and perhaps sometimes a custom class for a particular field would make sense, but many times all I need is a generalized validation rule. 

I mentioned previously that IDataErrorInfo checks the validation after the property has been updated.  This means if we attempt to insert a non-integer field with a non-integer value, the validity checking never occurs: instead, WPF swallows the error.  Using a validation rule causes the validation to occur before the property is updated, so we can easily prevent such an occurrence and report it to the user.  It does still allow the bad data into the TextBox, but that’s not necessarily a bad thing.

For this example, we need a validation rule that will report when the user attempts to insert a non-integer character.  We also want to be able to specify a range of acceptable values.  Additionally, we are going to add a few fields to help customize the messaging experience.  Here is the complete class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;

namespace CustomValidationRules
{
    public class IntegerValidationRule : ValidationRule
    {
        private int _min = int.MinValue;
        private int _max = int.MaxValue;
        private string _fieldName = "Field";
        private string _customMessage = String.Empty;

        public int Min
        {
            get { return _min; }
            set { _min = value; }
        }

        public int Max
        {
            get { return _max; }
            set { _max = value; }
        }

        public string FieldName
        {
            get { return _fieldName; }
            set { _fieldName = value; }
        }

        public string CustomMessage
        {
            get { return _customMessage; }
            set { _customMessage = value; }
        }


        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
        {
            int num = 0;

            if (!int.TryParse(value.ToString(), out num))
                return new ValidationResult(false, String.Format("{0} must contain an integer value.", FieldName));

            if (num < Min || num > Max)
            {
                if (!String.IsNullOrEmpty(CustomMessage))
                    return new ValidationResult(false, CustomMessage);


                return new ValidationResult(false, String.Format("{0} must be between {1} and {2}.",
                                           FieldName, Min, Max));
            }

            return new ValidationResult(true, null);
        }
    }
}

The magic all happens in the overridden Validate method.  There are lots of additional ways you could customize this, but there should be enough in this example to get you started writing your own custom validation rules.

I put generalized rules like this in a separate project and namespace so I can reuse them in many projects.  For rules that are specific to a particular DAL or Application you can just as easily put them in those namespaces.

Consuming Custom Validation Rules

Now that we have an IntegerValidationRule class, let’s add it to our XAML so we can put it into action.  Unfortunately, there is no facility in Blend for doing so, so we’ll have to edit the XAML directly.  The first thing to do is add a namespace reference to the XAML pointing to the location of the custom validation rules.

We are going to add the validation to a TextBox.  Normally, I would use the shortened syntax to specify a binding, but in this case I’m going to use long hand because there is a lot of detail to add. Here is the completed sample:

<TextBox
    x:Name="Int1TextBox"
    Validation.ErrorTemplate="{StaticResource validationTemplate}"
    Style="{StaticResource textBoxInError}"
    VerticalAlignment="Center"
    HorizontalAlignment="Center"
    MinWidth="50"
    Margin="0,0,6,0">
    <Binding
        Path="Int1"
        UpdateSourceTrigger="PropertyChanged"
        Mode="TwoWay">
        <Binding.ValidationRules>
            <ValidationRules:IntegerValidationRule
                Min="1"
                Max="9999999"
                FieldName="Int1" />
        </Binding.ValidationRules>
    </Binding>
</TextBox>

There are a number of things going on here.  First, notice we added the Validation.ErrorTemplate property.  This specifies a template to use when the error condition exists.  I’ll cover that shortly along with the Style property. Both of these are used to customize the display of the error condition.

Next, in the TextBox.Text property we’ve added a long hand version of Binding.  I use UpdateSourceTrigger to cue the validation check whenever the property changes.  If I use the default value the change will only occur once the element loses focus, and I prefer a more real time effect.

Within the Binding property, we’ve added a subproperty section called Binding.ValidationRules.  Inside this property we add references to the rules we wish to implement. This will create an instance of the custom validation rule with the specified property values.  Since I set default values in the class, I can leave off any property and the validation will still function. As you can see in the example above, this is where I can set the property values for this particular instance of my IntegerValidationRule.  In this case we are allowing the integer range to be between 1 and 9999999, and specifying the name we want the message to associate with any error.

That’s all we need to do to consume the custom rule.  Now we’ll take a look at the ControlTemplate and Style to see how we can control the display.

Styling a Custom Validation

I took both the ControlTemplate and the Style from the MSDN article How To Implement Binding Validation.  Here are the original values:

<ControlTemplate
    x:Key="validationTemplate">
    <DockPanel>
        <TextBlock
            Foreground="Red"
            FontSize="20"><Run
                Text="!" />
        </TextBlock>
        <AdornedElementPlaceholder />
    </DockPanel>
</ControlTemplate>
<Style
    x:Key="textBoxInError"
    TargetType="{x:Type TextBox}">
    <Style.Triggers>
        <Trigger
            Property="Validation.HasError"
            Value="true">
            <Setter
                Property="ToolTip"
                Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}" />
        </Trigger>
    </Style.Triggers>
</Style>

Insert these into the Windows.Resources section of the XAML.  If you’ve been coding along, you can run the sample now and it will work.  The only thing you really get is a red exclamation point to the left of the TextBox and a Tooltip if you hover over it.  This is OK, but I didn’t really feel it was enough to grab the user’s attention, so I’m going to change it just a little.

Before I do that, though, take a look at the ControlTemplate: there is something interesting here called an AdornedElementPlaceholder.  This nifty little element allows us to wrap the original ControlTemplate with additional elements.  In this sample, we are placing a TextBlock with the exclamation point just in front of our original control.  Feel free to experiment with this and add some code of your own around the offending TextBox.

Back to the Style: I want the TextBox to pop a little more when an error occurs.  Looking at the original Style code, we see that we have a Trigger that changes the Style when Validation.HasError is True.  In the default example, we are providing a ToolTip.  Here I’ve added some code to make the Background color of the TextBox Red and the Foreground White whenever an error occurs:

<Style
    x:Key="textBoxInError"
    TargetType="{x:Type TextBox}">
    <Style.Triggers>
        <Trigger
            Property="Validation.HasError"
            Value="true">
            <Setter
                Property="ToolTip"
                Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}" />
            <Setter
                Property="Background"
                Value="Red" />
            <Setter
                Property="Foreground"
                Value="White" />
        </Trigger>
    </Style.Triggers>
</Style>

Now the user should definitely notice when an error occurs!  Again, I recommend you experiment and see what else you can do to spice up your validation errors.

And another thing…

Another thing that would be very useful is to be able to bind to the state of validation.  In other words, I have a button that I only want enabled if there are no Validation errors.

In WinForms, I used to process through all the Controls and check to see if any of them had a non-blank entry in the errorProvider.  In WPF, what I would really like is to be able to bind IsEnabled to a “HasErrors” property somewhere.  Unfortunately I couldn’t make something so simple work.  I did, however, come up with a solution using a RoutedCommand.  Commands and commanding are a topic for another time, but here is the short version of what I did. 

I created a static RoutedCommand object in my Window class:

public static RoutedCommand ValidateEntryCommand = new RoutedCommand();

I then added the supporting CommandBinding to my XAML file, including CanExecute:

<Window.CommandBindings>
    <CommandBinding
        Command="{x:Static ValidationRulesPlay:Window1.ValidateEntryCommand}"
        Executed="CommandBinding_Executed"
        CanExecute="CommandBinding_CanExecute" />
</Window.CommandBindings>

In the CanExecute event handler, I call Validation.HasError(DependencyObject) for each TextBox I want to validate against and set the event args CanExecute property based on the results.

private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    bool hasError = Validation.GetHasError(Int1TextBox) ||
                    Validation.GetHasError(Int2TextBox);

    e.CanExecute = !hasError;
}

Lastly, I set the Command property of the SaveButton to my Command name.

<Button
    x:Name="SaveButton"
    Content="Button"
    Command="{x:Static local:Window1.ValidateEntryCommand}"/>

Now, the button will be disabled if either of the TextBoxes have an error condition.

Conclusion

I hope this distills some of the basics for you and gets you on your way to writing your own custom validation rules.  As always, comments are encouraged.

You can download the full solution for this project, which includes some supporting code.

Categories: WPF, XAML

Swatting A 6 Month Old Bug

August 4, 2009 Comments off

I began working on our first major WPF project last December.  Previously I had done a lot of playing and learning, and written a few internal utilities, but this was our first release grade project.  Things were going well and the partners were very happy with the software development… that is until “the bug”.

The Back Story

The software is an image management tool that interfaces with our legacy AS/400 application.  In short, it allows images on a network server to be linked with data records in our legacy application.  It is a replacement for an existing application written in FoxPro, so you can imagine the Oohs and Ahhs at the new UI.  The project also fills in some serious deficiencies in functionality.

As one can probably surmise, one important piece of functionality when managing groups of images is the ability to add, remove, or rename images.  With File IO such a rudimentary function, I had let it lapse until the end of the project.  Since WPF was the focus of my development, I had lost focus on the project itself.  I spent a lot of time making the application sing with animations, transitions, Visual States, drag and drop with a Visual Brush, you name it I wanted to try it.

Near the end of February I began working with the final bits, which included deleting an Image and saving an updated list of images back to the server.  As soon as I tried to perform any such action on any image, however, I would receive an IOException: "The process cannot access the file because it is being used by another process."

I worked intermittently on the issue while I continued improving the UI.  I figured it had to be something simple, so it took a little while until I began devoting serious effort to solving the problem.

Working on the Problem

Since the project was winding down, I began working on the problem in earnest about the middle of March.  I was able to determine that it was my application causing the lock, so I did a little research and found plenty of indication that opening images has a tendency to lock the file until the application ends.  With the idea that my application was holding the resource I tried to find a way to close it out until the operation was complete.

Without all the gory details, here is a list of things I tried in varying degrees, all of which failed to solve my problem:

  • Setting the ListBox.Source to null.
  • Adding a wait time before attempting the move.
  • Issuing GC.Collect().
  • Moving the operation to a different thread.

All of these were to no avail.  Eventually I posted the problem to StackOverflow and received two responses in short order.  I tried both of the solutions, but neither one seemed to solve my problem.  I asked a few friends on Twitter and email, but no one was able to help beyond what I had already tried.  By the end of April I was ready to call it quits.

Unfortunately for the project, but fortunately for my sanity, other projects became more important.  This gave me an official hiatus from the project, but I would still return to it occasionally in my spare time, always with the same results and frustrations.

Early in July my hiatus was over: the total project (the image project is a subproject of a larger effort) deadline is set at September 1st.  Panic set in: I have three applications to deliver in that time, and one of them is the Image manager.  I put it off a little while while I wrote one of the other applications, but on July 18th I returned full time to my nemesis.  The good news was there was little left to do: the bad news was that I had already spent countless hours on one stupid bug that was make or break for the entire application.

The Solution

I had little choice but to start from the beginning.  I went back to the StackOverflow discussion because it seemed to hold the most promise.  I wrote a test application that just performed the action I wanted, deleting an Image that was in the application.  By using a combination of the two methods in that thread I was able to get the feature working.

The solution that worked was to create a Value Converter that accepted the path of an image and returned a BitmapImage. 

[ValueConversion(typeof(string), typeof(BitmapImage))]
public class PathToBitmapImage : IValueConverter
{
    public static BitmapImage ConvertToImage(string path)
    {
        if (!File.Exists(path))
            return null;

        BitmapImage bitmapImage = null;
        try
        {
            bitmapImage = new BitmapImage();
            bitmapImage.BeginInit();
            bitmapImage.StreamSource = new FileStream(path, FileMode.Open, FileAccess.Read);
            bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
            bitmapImage.EndInit();
            bitmapImage.StreamSource.Dispose();
        }
        catch (IOException ioex)
        {
        }
        return bitmapImage;
    }

    #region IValueConverter Members

    public virtual object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null || !(value is string))
            return null;

        var path = value as string;

        return ConvertToImage(path);
    }

    public virtual object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }

    #endregion
}

I then bound the Image source to the path of the image using the converter. While this image (or rather a local copy of the image) was on the screen, I was able to delete the file from the server.  I had tried a version of this previously, but I assumed it wasn’t working because I was still getting the error. Now I had proof that this code works.

Applying the Solution

I was able to apply the solution to the original application after some refactoring.  It’s amazing how much my coding style has changed from the last 6 months experience with WPF.  I look at the application now and cringe, especially at the code behind.  Anyway, I was able to shoe horn my solution in and the images showed correctly.  Can you imagine the anguish I felt, however, when the first time I tried to delete an image I received the same error?

I was quite dismayed, until I realized that it was not the actual image code since that had been proven: there had to be something else causing the problem, but what?  I had already gone through all the code and every reference to an image with a fine toothed comb, several times, what more could there be?  I began the process over again, but I was not a happy camper.

And then it dawned on me.  I finally knew what the problem was: I had been bitten by Data Binding.

How Can I Blame Data Binding?

To be fair, the issue is not with Data Binding per se, but my own misunderstanding of the object I was binding.  The images were bound not once, or twice, but three times:

  • ImageListBox – each item is bound to a different image, effectively a FilmStrip control
  • CurrentImage – a larger Image control showing the selected image from ImageListBox, bound to ImageListBox.SelectedItem
  • DeleteImageDialogBox – a separate Window that displays the image and prompts the user to confirm deletion, the Image is bound to the path passed in from the parent Window

For both ImageListBox and DeleteImageDialogBox I had used the new Value Converter.  CurrentImage, however was a different story.

In binding CurrentImage to the SelectedItem property of the ListBox, I was expecting the property to represent the rendered image that was the result of the Value Converter.  This is completely wrong!  The SelectedItem property is still the file path string!  CurrentImage was still executing the default behavior and retrieving the file from the path resulting in a file lock.

The Resolution

Once I understood what CurrentImage was binding to, I knew what to do: apply the same Value Converter I was using for the list box items.  Sure enough, once I applied that Value Converter everything else fell in place.

In hindsight I feel this was an “Oh Duh” sort of problem.  So many times the solution to our most frustrating problems is right in front of us, and almost always the solution is fairly simple.  As I said to a friend recently – it’s amazing sometimes that we ever get anything right!

Now I have a new issue: via the Value Converter I’m going to the file system three times to show the same image.  I’ll have to address that at some point, and I have a few ideas.  I could store the BitmapImage copy, or a byte[], or a MemoryStream locally and reuse it.  For now, however, I’m going to leave well enough alone and get on to my next application.  Hopefully it won’t take six months to finish.

Categories: WPF

'Brush' type does not have a public TypeConverter class.

July 13, 2009 4 comments

I’ve been a big fan of Value Converters in WPF for some time now, but I was bitten today by something simple and thought it would be worth sharing.

Brush Converter

One of my favorite uses for Value Converters is to set colors of things based on object state.  In my current application, I have drawn a circle that I want to be filled in Red if the application is not connected to the database, and green if it is connected.  My binding engine has an “IsDatabaseConnected” boolean property that I can use for the binding, so what I need to do is bind the bool to a Brush. To do this, we create a simple converter:

using System.Windows.Data;
using System.Drawing;

namespace CamraSketchViewerWPF
{
    public class DatabaseConnectedColorConverter : IValueConverter
    {
        public Brush ConnectedBrush { get; set; }
        public Brush NotConnectedBrush { get; set; }

        #region IValueConverter Members

        public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value is bool)
            {
                var isConnected = (bool) value;
                return isConnected ? ConnectedBrush : NotConnectedBrush;
            }

            return null;
        }

        public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new System.NotImplementedException();
        }

        #endregion
    }
}

In the XAML we need an instance of the Converter to bind to: I used Blend 3 to write the XAML, but whether you do it this way or write it by hand you should end up with something like this:

<local:DatabaseConnectedColorConverter x:Key="DatabaseConnectedColorConverter" />
 

This will create an instance of the Converter as a resource that can then be used for the actual binding:

<Ellipse Fill="{Binding IsDatabaseConnected, Converter={StaticResource DatabaseConnectedColorConverter}, Mode=Default}" Width="15" Height="15"/>
 

If you run the code like so, you will get a transparent circle.  What’s missing are the property value declarations for the Converter. In addition to declaring the instance in XAML, we can also set its properties in XAML like so:

<local:DatabaseConnectedColorConverter x:Key="DatabaseConnectedColorConverter" ConnectedBrush="{StaticResource GreenBrush}" NotConnectedBrush="Red" />
 

Note that for ConnectedBrush I have defined my own version of Green as a resource. Now, if you run this code, you’ll receive the same exception that bit me:

‘Brush’ type does not have a public TypeConverter class.

It’s always the simplest things…

I have run into this message and others like it, and it is always frustrating.  What it means is that the Type I am trying to descibe in XAML does not have TypeConverter behavior defined: this is what allows simple Strings in XAML to be properly transcribed into correct types at runtime.

In this case, the problem was a simple one but not an evident one.  Now I love code completion and refactoring tools as much as the next guy, so whenever Intellisense volunteers to help me out I am quick to accept the assistance.  In this case, however, it did more harm than good!

When I referenced the Brush in my Value Converter class code, I was prompted by Intellisense to resolve the missing namespace, which I did with the handy-dandy Shift-Alt-F10 keystoke.  Only I did it reflexively and did not actually read my options.  If you look at the code above, you will see that the Value Converter class is referencing the System.Drawing namespace instead of the System.Windows.Media namespace.  System.Drawing.Brush was not written for WPF and so does not have the TypeConverter code necessary for this to work.  The problem is easily corrected by changing the namespace appropriately.

Conclusion

I placed this in the “Read your options” file for future use. If you are interested in more TypeConverter details, check out this article by Rob Relyea at XAMLfied.

Categories: WPF

Processing Multiline Text in a WPF TextBlock

March 23, 2009 1 comment

So I’m sure this will be filed in the *yawn* category for you experienced WPF developers out there, but I ran into an interesting item on Friday that I wanted to share.  I was developing a custom DialogBox that displays a lot of text and an image from a CLR object passed in from the calling Window.  The text is fairly long, three short paragraphs, but includes several bits of variable data (properties on the CLR object).

I had several options for how to go about this:

  1. Build the Text dynamically in the code behind.
  2. Create several TextBlock items and string them together, probably using WrapPanel, and bind the variables to the CLR properties.
  3. Store the desired text in the TextBlock.Text property with place holders and use it in a String.Format statement to insert the property values.  (I could have also stored this string in an external file)

I’m sure there are other options, but I didn’t want to spend a lot of time on this.  Option 1 just seemed too blunt, not to mention that I couldn’t see in Blend what it would look like from a design perspective.  Option 2 would probably work, and from a pure XAML approach would be preferable, but I was in a hurry so I went with what I knew (or so I thought) and chose option 3.

I immediately liked this idea because I could really see the end result in Blend.  To handle the variable embedding, I planned on reading in the TextBlock.Text value in the constructor and storing it in a private variable.  Then I could read the values passed in and reset the .Text property:

private string messageFormat ;
private DeleteImageDialogBox()
{
    this.InitializeComponent();
    messageFormat = MessageTextBlock.Text;
}

public DeleteImageDialogBox(PictureInfo pic, string archiveDirectory)
    : this()
{
    LayoutRoot.DataContext = pic;

    string message = String.Format(messageFormat,
        pic.GetRecordNumber(),
        pic.GetCardNumber(),
        archiveDirectory);

    MessageTextBlock.Text = message;
}

Unfortunately, this did not work.  A little debugging soon showed me why: TextBlock.Text was an empty string. This was confusing because Blend shows the Text property as containing my text.

Apparently, because I had CRLFs in my Text, the actual text was no longer exposed by the Text property, but was now part of the Inlines property.  Inlines is of type InlineCollection, a collection of Inline objects.  Inline objects are used to manage a line of content. Inline is an abstract class, so the actual collection can contain multiple types. In my case, I had to test for two types: LineBreak and Run.  In order to retain my formatting, if I detected a LineBreak I inserted “\r\n”.  If I detected a Run object, then I could grab the text.

So if you aren’t confused yet, or even if you are, here is the code I used to extract my full text into a usable string variable:

StringBuilder s = new StringBuilder();
foreach (var line in MessageTextBlock.Inlines)
{
    if (line is LineBreak)
    {
        s.Append("\r\n");
    }
    else if (line is Run)
    {
        Run text = (Run)line;
        s.Append(text.Text);
    }
}
messageFormat = s.ToString();

The only formatting I am retaining here are the LineBreaks: if my text had Underlines, Bolds, etc., I would need to add more code above to retain them.  This may seem like a lot of effort, but if I can definitely see the value for text formatting and processing.  I expect to see this again when I get deeper into WPF printing.

Categories: WPF

More Fun with WPF ScrollViewer

February 17, 2009 6 comments

In yesterday’s post, Fun with WPF ScrollViewer, I discussed how to detect when just the scroll bar was clicked on a draggable ListBox.  Today’s episode addresses something that happened to me on the other end: Dropping into the ListBox.

It probably makes sense that when a new item is dropped onto my ListBox, and therefore added to the list, I am setting the new item as the ListBox’s SelectedItem.  In theory, the ScrollViewer should automatically scroll the list so that the SelectedItem is in view.  In reality, this does not work (and I have it on good authority that it should.)  So in my scenario I am programmatically updating the SelectedItem, which now means I also need to programmatically instruct the ScrollViewer to scroll appropriately.

BringIntoView: The method that didn’t do anything

I want this code to run whenever the SelectionChanged event fires, so that is the method we will be using.  The first thing I tried was the BringIntoView method.  I tried it first on the ListBox itself, but realized that’s just silly since the ListBox is already in View.  What I really need is to tell the new SelectedItem to come into view.  If you have never done this, you may be surprised to find that you cannot cast the ListBox.SelectedItem object to a ListBoxItem: it is whatever type is DataBound to the List.  Instead, what I needed was a reference to the ListBoxItem that contains the SelectedItem.

In order to get the reference we need, we have to use the ListBox’s ItemContainerGenerator property.  This property exposes an ItemContainerGenerator object whose documentation is notably sparse.  But pull the object up in Intellisense and you’ll find a couple of useful methods, specifically ContainerFromItem and ContainerFromIndex.  We can use either one of these to retrieve a reference to the ListBoxItem container we need.  The code looks like this:

ListBoxItem item = (ListBoxItem)(ImageListBox.ItemContainerGenerator.ContainerFromItem(myListBox.SelectedItem));

Now that we have a reference to the ListBoxItem container, we can call its BringIntoView() method.

if (item != null)
{
    item.BringIntoView();
}

Unfortunately this was to no avail as it did not have the desired effect.  In fact, as far as I can tell, it had no effect, but it probably caused the ListBox’s RequestBringIntoView event to fire.  So, on to the next trial.

ScrollIntoView: The method that (almost) worked

Not to be thwarted, I began thumbing through Intellisense and found that ListBox has a method called ScrollIntoView.  For some strange reason, this sounded like it might be helpful, so I figured I’d give it a whirl.

ScrollIntoView (which is a really hard name to read in certain fonts because of the two lowercase ‘l’s followed by an uppercase ‘I’) requires that you pass it the object you want to scroll to, so we’ll pass it the ListBox.SelectedItem object:

myListBox.ScrollIntoView(myListBox.SelectedItem);

Now, if you run the code at first you will be lulled into a false sense of security.  This is the worst kind of bug, because it appears to work.  Change the SelectedItem and the ScrollViewer will adjust as necessary to scroll to the newly selected item… the first time.  What I found in subsequent drops to my ListBox was a very ugly bug.  It appears that the ScrollIntoView method gets caught in some kind of infinite loop, constantly resetting itself and causing the control display to gyrate wildly.  A little searching led me to this bug report at Microsoft.

This does not explain the exact same scenario, but it does exhibit the exact same results: an endless loop caused by a call to ListBox.ScrollIntoView(). Unfortunately, the bug is over a year old with no update or new information since it was accepted by Microsoft.

ScrollToHorizontalOffset: The method that does work

I was feeling pretty daunted at this point, but I decided to check one more thing before I quit for the day.  If I could get a reference to the ScrollViewer object itself, perhaps I could manually move it to the desired location.  I posted the code yesterday that I can use to retrieve the object reference, so all I had to do at this point was figure out what my options were for manually scrolling.

I found it in the ScrollToHorizontalOffset() method.  The method takes a double, which I mistakenly assumed was the number of screen units I wanted to scroll to, so I was trying to do a lot of math like measuring the ListBoxItem and multiplying the index number by the ActualWidth, and other pocket protector-y stuff like that.  Needless to say my first efforts were unrewarding.  I finally stumbled onto the fact the the HorizontalOffset indicates the index number of the item to scroll to – I was making it way too hard!  So all I had to do was pass the Selectedndex to the method and viola, a Scroll is born:

private void myListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (myListBox.SelectedIndex >= 0)
    {
                ScrollViewer scroll = myListBox.GetScrollViewer();
                double scrollPos = myListBox.SelectedIndex;
                scroll.ScrollToHorizontalOffset(scrollPos);
    }
}

Conclusion

I’m far from done with my drag and drop reorderable ListBox, but I’ve learned a lot about the VisualTree, item containers, and obviously the ScrollViewer. This code is certainly not set in stone: if you know another way to do this or have any recommendations for the above code, please share them below in the comments.

Categories: WPF

Fun with the WPF ScrollViewer

February 16, 2009 9 comments

In my current project, I have a horizontal ListBox wrapped in a ScrollViewer.  As you might expect, this indicates that I intend to have more content than my ListBox can display at any given time.  I am also implementing Drag and Drop reordering on this same ListBox.  This seemingly average task has turned out to be anything but, and I have spent the last couple of days fighting many issues, a couple of which are ScrollViewer specific.

Dragging and the scroll bar

The first problem I ran into was in initiating the Drag on the ListBox when the scrollbar was visible.  The problem is that the ScrollViewer is part of the default ListBox template, and the PreviewMouseLeftButtonDown event is wired to the ListBox.  This meant that if I tried to use the scroll bar, PreviewMouseLeftButtonDown would interpret it as a drag attempt and react accordingly.

In order to solve this, I needed to gain access to the ScrollViewer object that was part of the ListBox template, but there is no property for such an animal because the ListBox is completely unaware of the ScrollViewer.  And I could not access it directly because the ScrollViewer is not a named object within the Window, but rather it is part of the template.  So what’s a WPF developer to do?  The answer lies in navigating the VisualTree.

Navigating the VisualTree

In the code above, our XAML hierarchy ends at the ListBox:

Window
    LayoutRoot
        StackPanel
            ListBox

But the ScrollViewer lives inside the template definition of the ListBox.  So to access it at run time,we need to use the static VisualTreeHelper class.  To do that, we need to know what the template contains.  I used Blend to open a copy of the default ListBox Template which shows us the following hierarchy:

Template
    Border
        ScrollViewer
            ItemsPresenter

What we see is that the default ListBox template consists of a Border that holds a ScrollViewer that holds an ItemsPresenter.  This is important to visualize, because the ScrollViewer object we want is two levels into our ListBox, so we have to dig a little to get to it.  Our shovel for that digging is the VisualTreeHelper.GetChild() method.  I found this code posted by Matt Hohn at MSDN forums:

private ScrollViewer FindScroll()
{
    Border scroll_border = VisualTreeHelper.GetChild(PhotosListBox, 0) as Border;
    if (scroll_border is Border)
    {
        ScrollViewer scroll = scroll_border.Child as ScrollViewer;
        if (scroll is ScrollViewer)
        {
            return scroll;
        }
        else
        {
            return null;
        }
    }
    else
    {
        return null;
    }
}

In this code, Matt is using GetChild() to get the Border out of the Template.  The second time he uses the Child property to get the ScrollViewer from the Border.  I ended up moving this to an Extension Method so I could reuse it easily:

public static ScrollViewer GetScrollViewer(this ListBox listBox)
{
    Border scroll_border = VisualTreeHelper.GetChild(listBox, 0) as Border;
    if (scroll_border is Border)
    {
        ScrollViewer scroll = scroll_border.Child as ScrollViewer;
        if (scroll is ScrollViewer)
        {
            return scroll;
        }
        else
        {
            return null;
        }
    }
    else
    {
        return null;
    }
}

As you will see later, we will also need access to the ItemsPresenter within the ScrollViewer, so another simple Extension Method will do the trick:

public static ItemsPresenter GetItemsPresenter(this ListBox listBox)
{
    ScrollViewer scroll_viewer = listBox.GetScrollViewer();
    if (scroll_viewer is ScrollViewer)
    {
        ItemsPresenter list = scroll_viewer.Content as ItemsPresenter;
        if (list is ItemsPresenter)
        {
            return list;
        }
        else
        {
            return null;
        }
    }
    else
    {
        return null;
    }
}

Meanwhile, back at the Drag event…

So now that we can get a reference to the ScrollViewer, we have to determine whether or not the user is trying to scroll or drag an item within our ListBox.  To do this, we are going to start working in the ListBox_PreviewMouseLeftButtonDown event.  Remember that this event applies to the entire ListBox, so it will fire when the Scroll bar OR the ItemsPresenter are clicked.  To figure this out, we are going to check IsMouseOver to try to determine if the user clicked on the scroll bar or the actual items.  This gets a little interesting because the template hierarchy, and the inherent bubbling that occurs, means that when you click on the ItemsPresenter, both it AND the ScrollViewer will report IsMouseOver as true.  But if only the ScrollViewer is hovered over, then ItemsPresenter.IsMouseOver will report as false.

So we need references to both, and if ScrollViewer.IsMouseOver is true, but ItemsPresenter.IsMouseOver is false, then we can assume the user clicked on the scroll bar:

private bool IsScrolling { get; set; }
private void ImageListBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    IsScrolling = false;

    ScrollViewer scroll = ImageListBox.GetScrollViewer();
    ItemsPresenter presenter = ImageListBox.GetItemsPresenter();
    if (presenter != null && scroll != null)
    {
        IsScrolling = (scroll.IsMouseOver && !presenter.IsMouseOver);
    }
}

We are storing the IsScrolling variable outside our method, because we are going to use it in the ListBox_PreviewMouseMove event to determine whether we are Scrolling or Dragging:

private void ImageListBox_PreviewMouseMove(object sender, MouseEventArgs e)
{
    if (e.LeftButton == MouseButtonState.Pressed && !IsDragging && !IsScrolling)
    {
        // Initiate drag code
    }
}

Enough fun for now

In my next post, I will discuss how to force the ScrollViewer to scroll to my SelectedItem when it is programmatically selected.

Categories: WPF
Follow

Get every new post delivered to your Inbox.