Search code examples
wpfcomboboxitemcontainerstyleitemcontainergenerator

WPF: how to recreate ItemContainer?


Following my previous question How to change ComboBox item visibility, since the problem is slightly changed i decided to open a new post to solve the it. For those who don't want to read all the comments on the previous post, here is the situation.

I have a DataGrid that is generated at run time. Each column of this datagrid have a combobox inside the header. All those comboboxes have the same Source, that is an observable collection of a class item. Every item show a property that i use in the ItemContainerStyle of the comboboxes, to decide whether each ComBoBoxItem should be Visible or not.

Now, as far as i know, WPF work this way : if a view contain controls like combobox or treeview, then their items (i.e. ComboBoxItem, TreeViewItem...), won't be generated until it's not necessary (for example when the dropdown of a combobox is opened). If i apply an ItemContainerStyle, this will tell to the target how its items should be created. The problem is, that at the moment that this items are generated, every change i need to apply to the style, won't be saved.

Here is my code:

    <DataGrid HeadersVisibility="Column" Name="griglia" Grid.Row="2" ItemsSource="{Binding Path=Test}" AutoGenerateColumns="True" IsReadOnly="True" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Visible">
        <DataGrid.ColumnHeaderStyle>
            <Style TargetType="{x:Type DataGridColumnHeader}">
                <Setter Property="ContentTemplate" >
                    <Setter.Value>
                        <DataTemplate DataType="DataGridColumnHeader"  >
                            <ComboBox ItemContainerStyle="{StaticResource SingleSelectionComboBoxItem}" DisplayMemberPath="Oggetto" Width="100" Height="20" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},Path=DataContext.Selezione, UpdateSourceTrigger=LostFocus}"  SelectionChanged="SingleSelectionComboBox_SelectionChanged">
                            </ComboBox>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </DataGrid.ColumnHeaderStyle>
    </DataGrid>

ItemContainerStyle:

     <Style x:Key="SingleSelectionComboBoxItem" TargetType="ComboBoxItem" BasedOn="{StaticResource {x:Type ComboBoxItem}}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Selezionato}" Value="True">
                <!-- Hide it -->
                <Setter Property="Visibility" Value="Collapsed" />
                <!-- Also prevent user from selecting it via arrows or mousewheel -->
                <Setter Property="IsEnabled" Value="False" />
            </DataTrigger>
        </Style.Triggers>
    </Style>

SelectionChanged:

private void SingleSelectionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        foreach (var item in e.RemovedItems.OfType<PopolazioneCombo>())
        {
            item.Selezionato = false;
        }

        foreach (var item in e.AddedItems.OfType<PopolazioneCombo>())
        {
            item.Selezionato = true;
        }
    }

My requirement is that, if in any of the N combobox an item is selected, then, that item cannot be selected by anyone, until he lose the SelectedItem status. For instance let's assume i have 2 combobox and a collection of 4 Item (x,y,a,b) . If x is selected in ComboBox1, then x can't be selected in none of the 2 ComboBox until the SelectedItem of the ComboBox1 change (from x to y for instance). Now i can even accept the fact that the item in the dropdown is just disabled if it makes the things easier, i just need the fact that it cannot be selected again if he is already selected.

The problem is that this solution work for every ComboBox that has its ItemContainerGenerator.Status = NotStarted (this means that the ComboBoxItem are still not created). If i open the dropdown of a combobox, then its ComboBox items will retain their style no matter what i do (cause ItemContainerGenerator.Status = ContainersGenerated), while the combobox that i didn't opened keep track of the changes in the visibility of the items.

I'm Looking for a solution to recreate these items do that the new style with the changes in the visibility will be applied


Solution

  • after a lucky research on internet (i.e. i've found the correct combination of keyword), i found this wonderful method, so here the updated solution:

    private void ComboBox_DropDownOpened(object sender, EventArgs e)
        {
            ComboBox c = sender as ComboBox;
            c.Items.Refresh();
        }
    

    add this event in the xaml so that it became:

    <ComboBox DropDownOpened="ComboBox_DropDownOpened" ItemContainerStyle="{StaticResource SingleSelectionComboBoxItem}" DisplayMemberPath="Oggetto" Width="100" Height="20" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},Path=DataContext.Selezione, UpdateSourceTrigger=LostFocus}"  SelectionChanged="SingleSelectionComboBox_SelectionChanged"/>
    

    and magically the combobox show the correct changes. Now i'm not a big fan of code behind, so if there is a way to do this adding a property to my Collection, or via xaml it would be better