Feeds:
Posts
Comments

Posts Tagged ‘WPF’

I discovered this article, WPF Localization Using RESX Files, on codeproject.com when I was thinking about how to localize my WPF application, BarModelMaker. After having examined the sample code of the article, I decided to give it a try, and I successfully localized BarModelMaker to Simplified Chinese in 2 hours.

Thanks Grant Frisken for the excellent solution!

 

Read Full Post »

Customize WPF ComboBox

There is a column in the Lines ListView of the Edit Model dialog box of the GeometryViz3D tool for the user to choose the color of the lines. Previously the colors are shown as plain text in the ComboBoxes. It will be more user friendly if the colors can be illustrated as the actual colors as shown in the following screen shot.

Here is the XAML code I use to achieve this. The key idea is customize the ItemTemplate of the ComboBoxes bound to the Color column of the Lines ListView by drawing a Rectangle.

The ItemSource property of the ComboBox is bound to the Colors property of the EditModelViewModel object, which is an array of strings that are color names, including Black, Cyan, and so on. And therefore, each item of the ComboBox is bound to the name of a color. We bind the color name to the Fill property of the Rectangle by taking advantage of the WPF’s built-in feature that converts a color name to a Brush.

The remaining issue is how to specify the size of the Rectangle. If we don’t specify, the actual height and width will both be 0. After some research using the snoop tool, I decided to determine the Height based on the FontSize of the ComboBox, and the Width based on the ActualWidth the ComboBox. Since the ComboBox is an ancestor of the items of the drop down list, we use the ComboBox as the RelativeSource. To make the Height of the ComboBox similar to the ComboBoxes in other columns, we have to adjust the value a little bit. This is where the DoubleAdditionConverter comes in. We make the Height to be FontSize + 2, and Width to be ActualWidth – 4.

Since the Height and Width of the Rectangle will not change after initialized, we set the binding Mode to OneTime.

The SelectedValue property of the ComboBox is bound to the Color property of the G3DLine object that is bound to the ListViewItem.

<Window.Resources>
  <local:DoubleAdditionConverter x:Key="doubleAdditionConverter" />
  …
  <DataTemplate x:Key="colorCellTemplate">
    <ComboBox ItemsSource="{Binding Colors}"
              SelectedValue="{Binding Color}"
              Margin="-6, 0, -6, 0" >
      <ComboBox.ItemTemplate>
        <DataTemplate>
          <Rectangle Fill="{Binding Mode=OneTime}"
                     Height="{Binding Mode=OneTime,
                       RelativeSource={RelativeSource FindAncestor,
                         AncestorType={x:Type ComboBox}},
                       Path=FontSize,
                       Converter={StaticResource doubleAdditionConverter},
                       ConverterParameter=2}"

                     Width="{Binding Mode=OneTime,
                       RelativeSource={RelativeSource FindAncestor,
                         AncestorType={x:Type ComboBox}},
                       Path=ActualWidth,
                       Converter={StaticResource doubleAdditionConverter},
                       ConverterParameter=-4}" />
        </DataTemplate>
      </ComboBox.ItemTemplate>
    </ComboBox>
  </DataTemplate>
</Window.Resources>

<ListView Margin="3"
          ItemsSource="{Binding Lines}"
          SelectedValue="{Binding SelectedLine}">
  <ListView.View>
    <GridView>
      <GridView.Columns>
        …
        <GridViewColumn Header="Color" Width="120"
                        CellTemplate="{StaticResource colorCellTemplate}" />
      </GridView.Columns>
    </GridView>
  </ListView.View>
</ListView>

Here is the source code of the DoubleAdditionConverter:

public class DoubleAdditionConverter : IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        double v = (double)value;
        double p = Double.Parse((string)parameter);
        return v + p;
    }

    public object ConvertBack(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        double v = (double)value;
        double p = Double.Parse((string)parameter);
        return v - p;
    }

    #endregion
}

The entire source code can be found at the GeometryViz3D project on CodePlex.

Read Full Post »

Published an article on CodeProject, discussing about the issues related to binding ObservableCollection to ListView and ComboBox controls. Please read the article here.

Read Full Post »

In WPF, we can bind an ObservableCollection<T> object to the ItemsSource property of an ItemsControl, e.g. a ComboBox. When we add or remove an element from the collection, the control bound to the collection will get updated automatically. However, we modify an element of the collection, the control won’t get updated. To force the control gets updated when an element of the collection it is bound to, we need to write a class inheriting from ObservableCollection<T> and provide a method that calls the OnCollectionChanged() method, which raises the CollectionChanged event, which will caused the control bound to the collection to be refreshed.

public class ElementCollection<T> : ObservableCollection<T>
{
  public void UpdateCollection()
  {
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(
        NotifyCollectionChangedAction.Reset));
  }
}

Then we change the type of the collection to ElementCollection, and call the UpdateCollection() method after we changed the value of an element.

Read Full Post »

CommandManager.InvalidateRequerySuggested()

In a WPF application, I have several Button controls each bound to a ICommand property of a ViewModel. In one of the method of a ViewModel, I invoke a method with a worker thread. When the method is done, it will change the value of a property, which would causes the CanExecute() method of the ICommand objects to return a different value, which would, in turn, cause the Button controls to be enabled or disabled accordingly. But nothing changes. After doing some research, I’ve found that we can call the CommandManager.InvalidateRequerySuggested() method to force the Buttons bound to ICommand object to be refreshed.

private void DoSomething()
{
    ThreadPool.QueueUserWorkItem(state =>
        {
            DoSomethingInBackgroud();
            SynchronizationContext.Current.Post(s =>
                {
                    CommandManager.InvalidateRequerySuggested();
                }, null);
       });
}

The DoSomething() method will be called when the Execute() method of the ICommand object bound to a button is called. The ThreadPool.QueueUserWorkItem() method queues the lambda code block passed in to wait its turn to be run by a worker thread. Once the DoSomethingInBackground() method is done by the worker thread, another lambda code block containing the call to the CommandManager.InvalidateRequerySuggested() method is queued to the UI thread, represented by SynchronizationContext.Current, to make sure that this method will be executed by the UI thread.

Read Full Post »

To pratice the MVVM pattern, I’ve refactored the GeometryViz3D tool using CoreMVVM. Several lessons learned:

  • With the MVVM pattern, the code of some Views can be reduced to just a constructor. The plumbing between Views and ViewModels is done through data binding, making it possible to test the system “under the skin”, meaning that we will be able to unit-test the Model and the ViewModels, and the Views are just a thin skin, a XAML file.
  • When binding a property (source) of the ViewModel to the Text property of a TextBox, by default, the source will be updated when the TextBox loses the focus. It is possible to update the source as soon as the Text of the TextBox is changed by specifying the UpdateSourceTrigger property of the Binding to PropertyChanged.
    Text=”{Binding Path=X, UpdateSourceTrigger=PropertyChanged}”
  • In Windows Forms programming, we typically set the initial focus of a Window in the Loaded event. In WPF, we can set the initial focus using the FocusManager. The following example, set the initial focus to the TextBox named tbName.
    <Window

    FocusManager.FocusedElement=”{Binding ElementName=tbName}”>

Read Full Post »