Wednesday, April 26, 2017

WPF Tip #9 - ObservableCollection and the CollectionChanged Event

Tip #9 will examine something that seems like it should work according to the documentation on MSDN, but I have seen it not work as expected.

When using ObservableCollection<T>, if some code in your application needs to react when items are added or removed from the collection the CollectionChanged event can be handled.

private ObservableCollection<Pill> _pills = new ObservableCollection<Pill>();

public MainWindow()
{
    InitializeComponent();
    Pills.CollectionChanged += Pills_CollectionChanged;
}

public ObservableCollection<Pill> Pills
{
    get { return _pills; }
    set { _pills = value; }
}

void Pills_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    // Pills Collection Changed - do something
}

The issue I have encountered with this code is that setting that value of the Pills property directly does not always fire the CollectionChanged event.

Pills = someOtherPillCollection;

What I did to work around this was to remove the property setter and only work with the collection through its properties. In this case, by using Clear() and Concat<T>().

Pills.Clear();
Pills.Concat<Pill>(someOtherPillCollection);

ObservableCollection does not have an AddRange() method. You could write your own extension method to do it, but I prefer to use the LINQ extension method Concat<T>().

Has anyone else seen this same issue?

 

del.icio.us Tags: ,

Thursday, April 6, 2017

WPF Tip #8 - Grids and Rendering Performance

Today's tip is a quick one. It's about choosing the best panel for the layout of the UIElements in your WPF Window or Control.

Many developers will default to putting everything into a Grid. It's easy. They're flexible and offer many layout options. When a Window is relatively simple and performance is not being analyzed by users or stakeholders, using Grids everywhere might be ok. However, professional WPF developers should make it a practice to learn the differences in features and performance of each panel and choose the best one for a particular layout scenario.

In general, you should first try a StackPanel. If you can accurately lay out your Window with StackPanels without a deep nested web of them, they are the best way to go. They are much faster than Grids in both measurement of elements and layout/arrangement.

There are several other panels included in WPF. These are the most common, ranked from fastest to slowest (in general):

  1. Canvas
  2. StackPanel
  3. WrapPanel
  4. DockPanel
  5. Grid

This ranking is based on the accepted answer in this StackOverflow question. If you're interested in reading more about each of the panels and how they 'stack' up in different scenarios, I highly recommend reading the question and top answer.

Cheers!

 

del.icio.us Tags: ,

Thursday, March 30, 2017

WPF Tip #7 - Freeze WPF Freezable Objects from Code

In Tip #6, we took a vector image resource and set it to Freeze = True in the XAML markup. Suppose that you need the resource to freeze at some point later in the application's execution. You can simply add this code to that point in the application:
If mySimpleImage.CanFreeze Then
    ' Makes the vector image frozen.
    mySimpleImage.Freeze()
End If
Yes, I'm giving VB some love in today's tip.

Checking CanFreeze is necessary because not all Freezable objects can be frozen at every point in a WPF application's lifecycle. If Freeze() is called on an object that cannot currently be frozen, an exception will be thrown.

del.icio.us Tags: ,,

Monday, March 27, 2017

WPF Tip #6 - Freeze Vector Image Resources

The next several tips will examine Freezable objects. The description from MSDN:

A Freezable is a special type of object that has two states: unfrozen and frozen. When unfrozen, a Freezable appears to behave like any other object. When frozen, a Freezable can no longer be modified.

A few examples of WPF Freezable objects are Brushes, DrawingImages and Animation KeyFrames. If an application uses any of these resources and they are not going to be modified, they should be frozen. This will improve the performance of the application, as less processing will be used to re-render graphical elements.

Here is a quick example of Freezing a DrawingImage. Consider vector graphics used in fixed-size elements on a window (toolbar buttons, for example). These are good candidates to be frozen.

   <DrawingImage x:Key="SimpleImage" presentationOptions:Freeze="True"> 
      <DrawingImage.Drawing> 
         <DrawingGroup> 
            <DrawingGroup.Children> 
               <GeometryDrawing Brush="Black"> 
                  <GeometryDrawing.Geometry> 
                    <EllipseGeometry Center="50,50" RadiusX="50"  RadiusY="50"/> 
                  </GeometryDrawing.Geometry> 
                  <GeometryDrawing.Pen> 
                    <Pen Brush="Red" Thickness="2" /> 
                  </GeometryDrawing.Pen> 
               </GeometryDrawing> 
            </DrawingGroup.Children> 
         </DrawingGroup> 
     </DrawingImage.Drawing> 
   </DrawingImage>


Happy coding!


Wednesday, March 22, 2017

WPF Tip #5 - Data Binding with IMultiValueConverter

In Tip #4, we examined the use of IValueConverter with WPF binding. If you are using MulitBinding, a converter class that implements IMultiValueConverter is needed.

Like the IValueConverter, the class must implement Convert and ConvertBack methods. The difference being the multiple values come in as an object[] parameter in Convert() and go back out as an object[] return value in ConvertBack(). The following example takes two string values as input and returns them concatenated with a pipe '|'. It will convert them back using the Split function, assuming your text value contains no other pipes.

public class TextAppendMultiConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return String.Concat(values[0], "|", values[1]);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return (value as string).Split('|');
    }
}

This code will use the converter in a WPF TextBox's Text property MultiBinding.

<TextBox>
    <TextBox.Text>
        <MultiBinding Converter="{StaticResource TextAppendMultiConverter}" FallbackValue="Busy loading...">
            <Binding Path="IntroText"/>
            <Binding Path="ContentText"/>
        </MultiBinding>
    </TextBox.Text>
</TextBox>

The FallbackValue will be displayed if the converter fails or data is unavailable in the attached DataContext.

Next time, we will shift gears away from data binding. Happy coding!

 

del.icio.us Tags: ,,

Thursday, March 16, 2017

WPF Tip #4 - Data Binding with IValueConverter

The first three tips have all been related to WPF data binding. Let's continue the theme with IValueConverter. IValueConverter is used in conjunction with the Converter parameter on a property's Binding.

Converters are used whenever the type of the property being bound does not match the type of the property in the binding source. Converting a boolean value to Visibility enum value is the most common conversion throughout all Xaml code. It is so common that the .NET Framework has its own BooleanToVisibilityConverter class that implements IValueConverter.

To implement your own converter, simply create a class that implements the IValueConverter interface. The interface has two methods, Convert() and ConvertBack(). If your property will never update its binding source, the ConvertBack() method does not need to be implemented. If you leave the default code Visual Studio puts in newly generated methods to throw a NotImplementedException, you will quickly find out if the ConvertBack() method is necessary for your converter.

This simple example is a variation on the BooleanToVisibilityConverter. It is an IndecisiveEnumToVisibilityConverter. Here is what my IndecisiveEnum looks like:

public enum IndecisiveEnum
{
    Yes,
    Probably,
    ThinkSo,
    NotSure,
    No,
    WhyWouldAnyoneDoThat,
    NotUnlessEveryoneElseIsDoingIt
}

The converter needs to take one of these values and return a Visibility. Here's the implementation of Convert() and ConvertBack().

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    if (!(value is IndecisiveEnum))
        return Visibility.Collapsed;

    var typedValue = (IndecisiveEnum)value;

    switch (typedValue)
    {
        case IndecisiveEnum.Yes:
        case IndecisiveEnum.Probably:
        case IndecisiveEnum.ThinkSo:
            return Visibility.Visible;
        case IndecisiveEnum.No:
        case IndecisiveEnum.WhyWouldAnyoneDoThat:
            return Visibility.Collapsed;
        case IndecisiveEnum.NotUnlessEveryoneElseIsDoingIt:
            return IsEveryoneElseDoingIt() ? Visibility.Visible : Visibility.Collapsed;
        case IndecisiveEnum.NotSure:  // return a 50/50 random chance of yes/no
            Random gen = new Random();
            int prob = gen.Next(100);
            return prob <= 50 ? Visibility.Visible : Visibility.Collapsed;
        default:
            return Visibility.Collapsed;
    }
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
    if (!(value is Visibility))
        return IndecisiveEnum.No;

    var typedValue = (Visibility)value;

    switch (typedValue)
    {
        case Visibility.Collapsed:
        case Visibility.Hidden:
            return IndecisiveEnum.No;
        case Visibility.Visible:
            return IndecisiveEnum.Yes;
        default:
            return IndecisiveEnum.No;
    }
}

For the IndecisiveEnum.NotUnlessEveryoneElseIsDoingIt value, the case calls a method which we'll pretend calls a web service to find out if everyone else is actually doing it or not. If the input value is NotSure, then a random call will return either Visible or Collapsed. Converting back is a little more straightforward.

Consuming this converter in the WPF Xaml consists of two parts. First, the converter must be defined as a resource.

<Window.Resources>
    <local:IndecisiveEnumToVisibilityConverter x:Key="IndecisiveEnumToVisibilityConverter" />
</Window.Resources>

Finally, the resource can be used in the binding for our TextBox Visibility property.

<TextBox Visibility="{Binding IsVisible, Converter={StaticResource IndecisiveEnumToVisibilityConverter}}"/>

That's it. Next time we'll look at the IMultiValueConverter, which is used with MultiBinding.

 

del.icio.us Tags: ,,,

Monday, March 13, 2017

WPF Tip #3 - Using FallbackValue with MultiBinding

Let's take another look at using a FallbackValue in WPF. In Tip #2, it was used to provide a temporary value while waiting for an async data binding value to return from a slow-running ViewModel property getter.

A more common use of FallbackValue is in conjunction with MultiBinding. MultiBinding allows a single element's property to be bound to multiple objects on your data source (ViewModel, relative sources, etc.) If the values of the MultiBinding are unavailable or invalid, or use converters that return invalid results, the MultiBinding's FallbackValue will be displayed. This StackOverflow answer provides a great explanation with some examples.

Here is a simple example using the same Xaml file from Tip #2.

<TextBox Grid.Row="1">
     <TextBox.Text>
         <MultiBinding FallbackValue="Busy loading." StringFormat="Plain text is {0} and rich text is {1}.">
             <Binding Path="SlowText" IsAsync="True"/>
             <Binding Converter="{StaticResource RtfToPlainTextConverter}" Path="RichText"/>
         </MultiBinding>
     </TextBox.Text>
</TextBox>


The TextBox is now using a MultiBinding to return the original SlowText from last time as async plus some RichText property being converted to plain text with an RtfToPlainTextConverter that has been added to the project. If the bindings fail, the converters fail, or our async binding is really slow, the FallbackValue will be displayed in the TextBox.Text.

What is a converter, you ask? This sounds like a great subject for our next couple of WPF Tips. Stay tuned!

del.icio.us Tags: ,,