6

I created a ComboBox listing the colors that System.Windows.Media.Colors predefines, using the approach told in this question: How can I list colors in WPF with XAML?

My XAML code now is:

<Window ...>
    <Window.Resources>
        <ObjectDataProvider 
            ObjectInstance="{x:Type Colors}" MethodName="GetProperties" x:Key="ColorList" />
        <local:StringToBrushConverter x:Key="FontColorConversions" />
    </Window.Resources>

    <Grid Background="Black">

        ...

        <ComboBox  Grid.Column="1" Grid.Row="1" Height="22" Width="240" 
                   VerticalAlignment="Center" HorizontalAlignment="Left"
                   ItemsSource="{Binding Source={StaticResource ColorList}}"
                   SelectedValue="{Binding FontColor, Mode=TwoWay}"
                   DisplayMemberPath="Name"
                   SelectedValuePath="Name">
            <ComboBox.ItemContainerStyle>
                <Style TargetType="ComboBoxItem">
                    <Setter Property="Foreground" Value="{Binding Converter={StaticResource FontColorConversions}}"/>
                </Style>
            </ComboBox.ItemContainerStyle>
        </ComboBox>
        ...
    </Grid>
</Window>

And besides, please note that I bind SelectedValue to a VM class's FontColor property, which is of string type.

class FontSetting : INotifyPropertyChanged
{
    private string _fontColor = "Lavender";   // initial color
    public event PropertyChangedEventHandler PropertyChanged;

    public string FontColor
    {
        get
        {
            return _fontColor;
        }
        set
        {
            _fontColor = value;
            OnPropertyChanged("FontColor");
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

And I set the DataContext of the Window containing this ComboBox to an instance of FontSetting.

So each item in the ComboBox actually display a string representing a certain color now, what I want to do is set an item's Foreground color to that color its content indicates, like this:

enter image description here

Can anyone help? Thanks.

UPDATED: Since most of the solutions have a converter which converts string to Brush and actually I already have it, now I want to put mine here, as I binded a TextBox's Foreground to FontSetting's FontColor property, so that when you change the ComboBox, the color of that TextBox changes accordingly.

Here is my converter class, and it works fine by now:

   class StringToBrushConverter : IValueConverter
    {
        public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            BrushConverter conv = new BrushConverter();
            SolidColorBrush brush = conv.ConvertFromString("Lavender") as SolidColorBrush;
            if (null != value)
            {
                brush = conv.ConvertFromString(value.ToString()) as SolidColorBrush;
            }
            return brush;
        }

        public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return null;
        }
    }

When I click the ComboBox to open the dropdown list, I got an exception:

enter image description here

CONCLUSION

Amine's solution works, it's my mistake. I explain briefly now, if you bind a ComboBox to System.Windows.Media.Colors like what I am doing, when the item is rendered, the Convert() method of the converter class (which you assign to the binding) is executed, and actually the value passed to Convert() as its first parameter is a Syetem.Windows.Media.Color instance. I made mistake coz I thought it was of string type.

Therefore, in my case I need two converter classes, one converting string to Brush, and the other one converting Color to Brush. So I will keep my own StringToBrush converter and add Amine's ColorToBrush converter.

However, I simplified Amine's implementation a bit:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    BrushConverter conv = new BrushConverter();
    SolidColorBrush brush = SolidColorBrush)conv.ConvertFromString(FontSetting.DEFAULT_FONT_COLOR);
    if (null != value)
    {
        PropertyInfo pi = value as PropertyInfo;
        if (null != pi)
        {
            brush = conv.ConvertFromString(pi.Name) as SolidColorBrush;
        }
    }
    return brush;
}

Moreover, Joe's input is also valuable, put all them together, I can keep the items' color consistent, which is perfect.

Community
  • 1
  • 1
VincentZHANG
  • 757
  • 1
  • 13
  • 31

2 Answers2

5

You can set Style of ComboBoxItem as bellow :

    <ComboBox  Grid.Column="1" Grid.Row="1" Height="22" Width="240" x:Name="CB"
               VerticalAlignment="Center" HorizontalAlignment="Left"
               ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
               DisplayMemberPath="Name"
               SelectedValuePath="Name">
        <ComboBox.ItemContainerStyle>
            <Style TargetType="ComboBoxItem">
                <Setter Property="Foreground" Value="{Binding Converter={StaticResource converter}}"/>
            </Style>
        </ComboBox.ItemContainerStyle>
    </ComboBox>

By using this converter:

public class MyConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        object obj = ((System.Reflection.PropertyInfo)value).GetValue(this,null);           
        return (SolidColorBrush)new BrushConverter().ConvertFromString(obj.ToString());
    }

    public object ConvertBack(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        return value;
    }
}

enter image description here

Amine
  • 1,198
  • 11
  • 19
  • Thanks, but in your code: `Value="{Binding Converter={StaticResource converter}}"`, did you miss anything? you don't need to indicate from what source your binding gets the value? – VincentZHANG Apr 27 '16 at 00:31
  • btw, yr code doesn't work, though. I believe you r on the right track, just we need to know how refer to a MenuItem's value when binding in that ItemContainerStyle tag. – VincentZHANG Apr 27 '16 at 01:23
  • My code works as shown in the screenshot. Did you get any error or exeption? – Amine Apr 27 '16 at 07:31
  • Value="{Binding Converter={StaticResource converter}}". No you it's not necessery to specify the value. The code works fine. – Amine Apr 27 '16 at 07:33
  • Thank you. If I use my own version of converter implementation, I got exception saying "token is not valid", I update the post, plz see it for details. If I use yr version of converter, I got exception when I compile it, saying: "Unable to cast object of type 'System.String' to type 'System.Reflection.PropertyInfo'." – VincentZHANG Apr 27 '16 at 08:31
  • Ok, so your xaml is not correct. Can you please, show me your xaml (with my solution). – Amine Apr 27 '16 at 08:55
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/110352/discussion-between-vincentzhang-and-amine). – VincentZHANG Apr 27 '16 at 09:07
1

Two ways, use a value converter or an intermediate property. The easiest is probably an intermediate property as you are using well structured bindings already for your SelectedItem (but value converters are fun too!).

SelectedValue is bound to FontColor, so in your setter set another value:

public string FontColor
{
    get
    {
        return _fontColor;
    }
    set
    {
        _fontColor = value;
        ForegroundColorToDisplay = GetBrushFromColorString(value);
        OnPropertyChanged("FontColor");
    }
}

public Brush _foregroundColorToDisplay
public Brush ForegroundColorToDisplay
{
    get
    {
        return _foregroundColorToDisplay;
    }
    set
    {
        _foregroundColorToDisplay= value;
        OnPropertyChanged("ForegroundColorToDisplay");
    }
}

or, if you don't want to store it:

public string FontColor
{
    get
    {
        return _fontColor;
    }
    set
    {
        _fontColor = value;
        //note it fires two changed events!
        OnPropertyChanged("ForegroundColorToDisplay");
        OnPropertyChanged("FontColor");
    }
}

public Brush ForegroundColorToDisplay
{
    get
    {
        return GetBrushFromColorString(value);;
    }
}

You can bind to this new property in your xaml:

<ComboBox  Grid.Column="1" Grid.Row="1" Height="22" Width="240" 
               VerticalAlignment="Center" HorizontalAlignment="Left"
               ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
               SelectedValue="{Binding FontColor, Mode=TwoWay}"
               DisplayMemberPath="Name"
               SelectedValuePath="Name"
               Foreground="{Binding ForegroundColorToDisplay, Mode=OneWay}"/>

If you are interested, a value converter would work like this:

Create a class in your code behind (or elsewhere if it's going to be used a lot) that implements IValueConverter, takes the string and returns a Brush:

public class StringToBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        return GetBrushFromString(value as string)
    }

    public object ConvertBack(object value, Type targetType, 
        object parameter, CultureInfo culture)
    {
        Throw new SomeException();
    }
}

and use it in your binding in xaml to convert the selectedItem binding to Foreground brush:

<Window.Resources>
    <local:StringToBrushConverter  x:Key="converter" />
</Window.Resources>
...
<ComboBox  Grid.Column="1" Grid.Row="1" Height="22" Width="240" 
               VerticalAlignment="Center" HorizontalAlignment="Left"
               ItemsSource="{Binding Source={StaticResource colorPropertiesOdp}}"
               SelectedValue="{Binding FontColor, Mode=TwoWay}"
               DisplayMemberPath="Name"
               SelectedValuePath="Name"
               Foreground="{Binding FontColor, Mode=OneWay, Converter={StaticResource converter}}"/>
Joe
  • 6,773
  • 2
  • 47
  • 81
  • 1
    thank you for your detailed answer, but it just colors the selected item after the dropdown list collapses, what I want is each item is colored in that dropdown list, please refer to the picture in my question.thank you anyway. – VincentZHANG Apr 27 '16 at 00:59
  • Ah, my fault, should have known that would happen. Didn't think through my logic properly. – Joe Apr 29 '16 at 08:32