Brothers In Code

...a serious misallocation of .net resources

Grouping TreeView Items in WPF

I had a view model that consisted of a linked list of nodes.  I wanted to group one set of nodes so I decided to try out the grouping functionality of CollectionViewSource.

Here's my XAML:


    <HierarchicalDataTemplate x:Key="ActionNodeTemplate">
      <tool:ActionNavigatorActionNodeUC />
    </HierarchicalDataTemplate>   
    <HierarchicalDataTemplate x:Key="GroupTemplate" ItemsSource="{Binding Items}" ItemTemplate="{StaticResource ActionNodeTemplate}">
      <Label Foreground="Green" Content="{Binding Path=Name}" Name="NodeLabel"/>
    </HierarchicalDataTemplate>
    <HierarchicalDataTemplate DataType="{x:Type an:ActionProjectSummaryNode}" ItemsSource="{Binding SubItemsView.View.Groups}" ItemTemplate="{StaticResource GroupTemplate}">
      <Label Foreground="Red" Content="{Binding Path=DisplayText}" Name="NodeLabel"/>
    </HierarchicalDataTemplate>
    <HierarchicalDataTemplate DataType="{x:Type an:RootActionNavigatorNode}" ItemsSource="{Binding SubItems}">
      <Label Foreground="Blue" Content="{Binding Path=DisplayText}" Name="NodeLabel"/>
    </HierarchicalDataTemplate>

ProjectActionSummaryNode exposed a CollectionViewSource of ActionNode items named SubItemsView.  In that node i grouped by the Data.ActionTypeDescription with:


       SubItemsView.GroupDescriptions.Clear();
       SubItemsView.GroupDescriptions.Add(new PropertyGroupDescription("Data.ActionTypeDescription"));

That was pretty much it.  For nodes of type ActionProjectSummaryNode, ItemsSource was set to SubItemsView.View.Groups.  Groups is a ReadOnlyObervableCollection of CollectionViewGroup objects (CollectionViewGroupInternal actually).  Each one has a Name property that represents the Group (in this case Data.ActionTypeDescription), and a Items Property that contains the original items that were grouped (ActionNodes).

WPF ToolBar Buttons Do Not Cause Loss Of Focus

Consider this little WPF Window:


    <DockPanel>
      <ToolBar DockPanel.Dock="Top" >
        <Button Content="Save" Name="BtnSave" Click="BtnSave_Click" />
      </ToolBar>
      <StackPanel>
        <TextBox Name="IptTextBox" Text="{Binding Content, ElementName=OptLabel}"  />
        <Label Name="OptLabel" Content="Hello" />
        <Button Content="Lose Focus" />
      </StackPanel>               
    </DockPanel>

If you type something into the text box and then click the "Lose Focus" button, the label is updated as expected (since the default UpdateSourceTrigger on the binding is on focus).  However, if you click on the save button in the toolbar, the label is not updated.

According to MSDN, ToolBars, in addition to a couple of other objects, have a different "Logical Focus Scope." To fix this problem, I added the following code to my save button:


      Control focusedElement = FocusManager.GetFocusedElement(this) as Control;
      if (focusedElement != null)
      {
        focusedElement.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
        focusedElement.Focus();
      }

WPF ComboBox's SelectedValue Property Doesn't Update Binding Source

A quick google search will find you a ton of information on this topic.  Unfortunately the majority of them are simple path issues with a binding expression.  I had a somewhat unique issue from a very simple mistake.

I had the following ComboBox:


          <ComboBox Grid.Row="7" Grid.Column="1" Name="IptGpSalesPersonSlprsnid2" SelectedValue="{Binding Data.SLPersonId, ValidatesOnDataErrors=True}" ItemsSource="{Binding SalesPeople}" DisplayMemberPath="DisplayName" SelectedValuePath="SalesPersonId" />

This mostly did what I expected.  It displayed "DisplayName" for the entire list of objects, it showed "SalesPersonId" as the SelectedValue, it would update another TextBox with the SelectedValue, and the selection could be changed by changing the SelectedValue in that TextBox.  But for the life of me, it would not update the SLPersonId property on the "Data" object it was bound too. 

"Data" is of type DynamicObject that I'm using as a binding proxy.  I really didn't expect anything wrong with it since a TextBox bound to the same property would update the property without a problem.  Still I trolled the overridable properties of DynamicObject and ended up adding the default overrides for the Equals and TryConvert methods.  Sure enough, upon selecting a new item from the ComboBox, the debugger broke on "Equals."  To my surprise, base.Equals was actually returning false.  Why?  This should be true since this was a reference type and I was returning the same object....damn....there's my problem...

In my view-model, my data property looked like this:


    public DynamicProxy Data
    {
      get { return new DynamicProxy(this.data); }
    }

I quickly added that code as a test and never made a variable to hold the new DynamicProxy instance.

Why ComboBox checks the variable instances to see if they are the same, i don't know, but since i look at what I did as bad code anyway, it doesn't really matter :P.

 

B

Animate A WPF Element Based On Its Visibility Property

I had a combo box was being displayed based on the the value of another control.  To call attention to it's appearance, I wanted it to "fly in" by animating its size.  Below is my WPF style.  There are a couple of things that I couldn't figure out.  First, having it "fly away" in the same fashion is out since it disappears before any animation takes place (although I have seen people do it in code behind).  Second, I'm not quite sure why I need the first setter value.  I seems like the one in the trigger should work but by itself, it wasn't enough.


    <Style TargetType="{x:Type ComboBox}" >
      <Setter Property="RenderTransform">
        <Setter.Value>
          <ScaleTransform ScaleX="0" ScaleY="0"/>
        </Setter.Value>
      </Setter>
      <Style.Triggers>
        <Trigger Property="Visibility" Value="Visible">
          <Trigger.EnterActions>
            <BeginStoryboard>
              <Storyboard>
                <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" To="1" Duration="00:00:2">
                  <DoubleAnimation.EasingFunction>
                    <PowerEase Power="3" EasingMode="EaseInOut"/>
                  </DoubleAnimation.EasingFunction>
                </DoubleAnimation>
                <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" To="1" Duration="00:00:2">
                  <DoubleAnimation.EasingFunction>
                    <PowerEase Power="3" EasingMode="EaseInOut"/>
                  </DoubleAnimation.EasingFunction>
                </DoubleAnimation>
              </Storyboard>
            </BeginStoryboard>
          </Trigger.EnterActions>
        </Trigger>
        <Trigger Property="Visibility" Value="Hidden">
          <Setter Property="RenderTransform">
            <Setter.Value>
              <ScaleTransform ScaleX="0" ScaleY="0"/>
            </Setter.Value>
          </Setter>
        </Trigger>
      </Style.Triggers>
    </Style>

Error: <Object> is not a valid value for property 'Target' in WPF

I'm still pretty new to WPF so you can chalk this one up to inexperience.  I was trying to make a textbox scale in/scale out based on a trigger monitoring visibility.  My thought is I needed to set Storyboard.Target="RenderTransform" and Storyboard.TargetProperty="ScaleX" but doing so got me the above error.  The problem is that Target expects a dependency object and RenderTransform is an attached property.  The simple fix was just to set Storyboard.TargetProperty="RenderTransform.ScaleX.

Keyboard Input Differences Between Winforms and WPF

I'm currently converting a winforms app into a WPF app and discovered the key events are significantly different in WPF.  Here's a few tips and things to look out for:

  • The old KeyPressEventArgs.KeyChar is just a char and included the modifier key stroke (CTRL-S = 'S'-64 = (Char)19, SHIFT-S = 's'-32 = 'S').
  • The new KeyEventArgs.Key is an enum that includes every key.  Modifier keys are their own key stroke.
  • (Char)KeyInterop.VirtualKeyFromKey(e.Key) will get you the char value from the new enum, although it is always upper case. 
  • Since modifier keys raise an event in WPF, I find I'm ignoring them alot:

    //ignore modifier keys
    if (e.Key == Key.LeftCtrl ||
            e.Key == Key.RightCtrl ||
            e.Key == Key.LeftAlt ||
            e.Key == Key.RightAlt ||
            e.Key == Key.LeftShift ||
            e.Key == Key.RightShift)
    {
      e.Handled = false;
      return;
    }