(Xaml/WPF) DataGridTemplateColumn -> DataTemplate -> ComboBoxItem SelectionChanged

53 Views Asked by At

Greetings StackOverflow,

I've been working on a function that combines PowerShell classes with Xaml/WPF that has multiple tabs that have multiple DataGrid objects in some cases.

I've been having an issue with my approach, and it might be too complicated to post all of the code, so I'll elaborate on the specific issue I'm having.

In many of my GUI applications, I create custom classes in PowerShell that use DataBinding to tie those classes to any particular DataGrid that uses any variation of the following:

  • DataGridTextColumn
  • DataGridCheckBoxColumn
  • DataGridComboBoxColumn
  • DataGridTemplateColumn

I've been writing many applications that use the first (3) just fine for quite some time. However, the DatGridComboBoxColumn does not seem to work in the manner that I would like it to, and thus, using the DataGridTemplateColumn is basically my last vestige, and it occasionally messes me up depending on what I'm trying to do with it.

In this particular case, I'm going to describe (1) of the tabs and its' main DataGrid and how I'm attempting to use it.

This DataGrid effectively renders the state of a particular "Get-HotFix" item, albeit customized to have more properties. Here is that class that I want to be represented in the DataGrid.

Class HotFixItem
{
    [UInt32]         $Index
    [UInt32]       $Profile
    [String]        $Source
    [String]      $HotFixID
    [String]   $Description
    [String]   $InstalledBy
    [String]   $InstalledOn
    [Object]         $State
    [Object]        $Target
    [String]        $Status
    HotFixItem([UInt32]$Index,[Object]$HotFix)
    {
        $This.Index       = $Index
        $This.Source      = $HotFix.PSComputerName
        $This.Description = $HotFix.Description
        $This.HotFixID    = $HotFix.HotFixID
        $This.InstalledBy = $HotFix.InstalledBy
        $This.InstalledOn = ([DateTime]$HotFix.InstalledOn).ToString("MM/dd/yyyy")

        $This.SetStatus()
    }
    SetState([Object]$State)
    {
        $This.State       = $State
    }
    SetTarget([Object]$Target)
    {
        $This.Target      = $Target
    }
    SetProfile([UInt32]$xProfile,[Object]$Target)
    {
        $This.Profile     = $xProfile
        $This.SetTarget($Target)
    }
    SetStatus()
    {
        $This.Status      = "[HotFix]: {0} {1}" -f $This.InstalledOn, $This.HotFixId
    }
    [String] ToString()
    {
        Return "HotFix.Item"
    }
}

At this point, I have a controller class that has an associated profile class where I can export and import the state of the HotFix. With all of the hot fixes on the system, it adds various properties such as Index, Profile, and Target... which is what the profile class handles for serializing the output to a file, and then it allows the same utility to deserialize and import that particular entry.

Here is a look at the chunk of Xaml that handles that class, specifically:

<DataGrid Grid.Row="4"
    Name="HotFixOutput"
    SelectionMode="Extended">
    <DataGrid.RowStyle>
        <Style TargetType="{x:Type DataGridRow}"
                BasedOn="{StaticResource xDataGridRow}">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="ToolTip">
                        <Setter.Value>
                            <TextBlock Text="{Binding Description}"
                                        Style="{StaticResource xTextBlock}"/>
                        </Setter.Value>
                    </Setter>
                    <Setter Property="ToolTipService.ShowDuration" 
                            Value="360000000"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </DataGrid.RowStyle>
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="[+]" Width="25">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <CheckBox IsChecked="{Binding Profile, 
                                            Mode=TwoWay, 
                                            NotifyOnSourceUpdated=True, 
                                            NotifyOnTargetUpdated=True, 
                                            UpdateSourceTrigger=PropertyChanged}">
                        <CheckBox.LayoutTransform>
                            <ScaleTransform ScaleX="0.9" ScaleY="0.9"/>
                        </CheckBox.LayoutTransform>
                    </CheckBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTextColumn Header="#"
                            Binding="{Binding Index}"
                            Width="40"/>
        <DataGridTextColumn Header="Source"
                            Binding="{Binding Source}"
                            Width="*"/>
        <DataGridTextColumn Header="HotFix ID"
                            Binding="{Binding HotFixID}"
                            Width="80"/>
        <DataGridTextColumn Header="Installed By"
                            Binding="{Binding InstalledBy}"
                            Width="*"/>
        <DataGridTextColumn Header="Installed On"
                            Binding="{Binding InstalledOn}"
                            Width="120"/>
        <DataGridTemplateColumn Header="State"
                                Width="100"
                                IsReadOnly="True">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox SelectedIndex="{Binding State.Index}"
                                Style="{StaticResource DGCombo}">
                        <ComboBoxItem Content="Installed"/>
                        <ComboBoxItem Content="Remove"/>
                        <ComboBoxItem Content="Install"/>
                    </ComboBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
        <DataGridTemplateColumn Header="Target"
                                Width="100">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox SelectedIndex="{Binding Target.Index}"
                                Style="{StaticResource DGCombo}">
                        <ComboBoxItem Content="Installed"/>
                        <ComboBoxItem Content="Remove"/>
                        <ComboBoxItem Content="Install"/>
                    </ComboBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

The controller class also has an array of...

Enum HotFixStateType
{
    Installed
    Remove
    Install
}

Class HotFixStateItem
{
    [UInt32]       $Index
    [String]        $Name
    [String]       $Label
    [String] $Description
    HotFixStateItem([String]$Name)
    {
        $This.Index = [UInt32][HotFixStateType]::$Name
        $This.Name  = [HotFixStateType]::$Name
    }
    [String] ToString()
    {
        Return $This.Name
    }
}

And this enum and class are used to populate the ComboBox properties "State" and "Target".

    [Object]         $State
    [Object]        $Target

When the file is deserialized, it reproduces this array of objects and selects the correct item based on the property "Name", that way the ComboBox can effectively select the correct name, and use the index property to change the state of the ComboBox.

The idea is that the GUI will allow the "Target" class to be switchable (if desired), to "Remove" or "Install".

The problem is... when the profile class goes to collect the data from the collection, the GUI is not propagating the changes being made in the XAML to the code behind.

For the profile, that's working perfectly. For the Target property, it isn't. It's retaining the original value that PowerShell feeds to it, basically ignoring the changes being made with the ComboBox item in the GUI.

Further to that point, I've tried using the Binding="{}" properties:

Mode=TwoWay,
NotifyOnSourceUpdated=True, 
NotifyOnTargetUpdated=True, 
UpdateSourceTrigger=PropertyChanged

...on the ComboBox items in the DataTemplates. However, what happens when I do THAT, is that ALL of the ComboBox template objects in the whole DataGrid are changed... which is, definitely not desirable.

I'm not using C# to do any of this, but if I see a C# approach that works, I can try to implement that. Fact is, many of the suggestions I've found don't work. Or, some of the solutions speak to methods that I can't access or aren't even available.

Because of the content that PowerShell classes are providing to the Xaml, I'm not entirely sure how to get the ComboBox selection for "Target" to propagate to the code behind. I've used the ItemsSource property before, but it crashes if the array is empty or null, thus I've been using the Items property instead...

Regardless, it appears as if the ComboBoxes are merely cosmetic. They're not retaining the information when the profile class saves the information to an outfile.

Any help or suggestions would be appreciated.

0

There are 0 best solutions below