1
votes

I'm trying to create a dialog with a dynamic combobox that changes the colour of the items' text depending on what is other comboboxes have as their selected items. I've done this by having the combobox's dropdownopened event start a loop which tests each comboboxitem's value against the selected value of the other combobox, changing the colour depending on whether it's greater or smaller.

It mostly works using ComboBox.ItemContainerGenerator to customise the comboboxitems directly, but it doesn't update the colours until the second time the combobox is opened.

I've recreated this problem with much simpler comboboxes:

XAML:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DisposableXAML"
        xmlns:Properties="clr-namespace:DisposableXAML.Properties" 
        x:Class="DisposableXAML.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="306" Width="396">
    <Canvas>
        <ComboBox x:Name="combobox1" Canvas.Left="129" Canvas.Top="44" Width="120" SelectedIndex="0"/>

        <ComboBox x:Name="combobox2" Canvas.Left="129" Canvas.Top="117" Width="120" DropDownOpened="combobox2_DropDownOpened"/>
    </Canvas>
</Window>

C#:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace DisposableXAML
{

    public partial class MainWindow : Window
    {
        List<int> intList;

        public MainWindow()
        {
            InitializeComponent(); 

            DataContext = this;
            intList = new List<int>() { 1, 2, 3, 4 };

            combobox1.ItemsSource = intList;
            combobox2.ItemsSource = intList;
        }

        private void combobox2_DropDownOpened(object sender, EventArgs e)
        {
            int SelectedNum = (int) combobox1.SelectedItem;

            for (int i = 0; i < 4; i++)
            {
                ComboBoxItem item = (ComboBoxItem)combobox2.ItemContainerGenerator.ContainerFromIndex(i);;

                if (item != null)
                {
                    if (i < SelectedNum)
                    {
                        item.Background = Brushes.Green;
                    }
                    else
                    {
                        item.Background = Brushes.Blue;
                    }
                }
            }
        }
    }
}

Combobox on first open:

Combobox on first open

Combobox on second open:

Combobox on second open

Is there some way to get the combobox to update on the first open? Or is there a cleaner way I can achieve this effect?

1
You could do it more cleanly in the XAML by setting up varying styles with DataTriggers or something along those lines. It might require a converter depending on how you do it. - Herohtar
This isn't what ItemContainerGenerator is for; ItemContainerStyle in the XAML with triggers would be a far better choice. With XAML, stuff like this is almost always best done in the XAML. - 15ee8f99-57ff-4f92-890c-b56153

1 Answers

1
votes

This is one way to do it that uses mostly just XAML and a single MultiValueConverter. This example sets the background color of the items in the second ComboBox based on whether the selected item in the first ComboBox is greater, equal, or less.

MainWindow.xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <local:ComparisonConverter x:Key="ComparisonConverter" />
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="150" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
            <RowDefinition />
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <ComboBox Name="Box1" ItemsSource="{Binding Items}" Grid.Column="1" Grid.Row="1" />
        <ComboBox Name="Box2" ItemsSource="{Binding Items}" Grid.Column="1" Grid.Row="3">
            <ComboBox.ItemContainerStyle>
                <Style TargetType="ComboBoxItem">
                    <Style.Triggers>
                        <DataTrigger Value="GT">
                            <DataTrigger.Binding>
                                <MultiBinding Converter="{local:ComparisonConverter}">
                                    <Binding ElementName="Box1" Path="SelectedItem" />
                                    <Binding />
                                </MultiBinding>
                            </DataTrigger.Binding>
                            <Setter Property="Background" Value="Red" />
                        </DataTrigger>
                        <DataTrigger Value="LT">
                            <DataTrigger.Binding>
                                <MultiBinding Converter="{local:ComparisonConverter}">
                                    <Binding ElementName="Box1" Path="SelectedItem" />
                                    <Binding />
                                </MultiBinding>
                            </DataTrigger.Binding>
                            <Setter Property="Background" Value="Blue" />
                        </DataTrigger>
                    </Style.Triggers>
                    <Setter Property="Background" Value="Green" />
                </Style>
            </ComboBox.ItemContainerStyle>
        </ComboBox>
    </Grid>
</Window>

MainWindow.xaml.cs:

using System.Collections.Generic;
using System.Windows;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private List<int> items;

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
            this.items = new List<int>(new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
        }

        public List<int> Items
        {
            get
            {
                return this.items;
            }
        }
    }
}

ComparisonConverter.cs:

using System;
using System.Linq;
using System.Windows.Data;
using System.Windows.Markup;

namespace WpfApplication1
{
    public class ComparisonConverter : MarkupExtension, IMultiValueConverter
    {
        public ComparisonConverter()
        {
        }

        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string result = null;
            if (values.Count() >= 2)
            {
                int value1 = System.Convert.ToInt32(values[0]);
                int value2 = System.Convert.ToInt32(values[1]);

                if (value1 > value2)
                {
                    result = "GT";
                }
                else if (value1 < value2)
                {
                    result = "LT";
                }
                else
                {
                    result = "EQ";
                }
            }

            return result;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }
}