WPF - Bind ComboBox Item Foreground to Its Value - c#

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:
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:
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.

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;
}
}

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}}"/>

Related

How to dynamically change border color when the error occur in UWP XAML?

I had bind the error method property with text-block so that when error occurs it is shown down the textbox in the form. But i also want to change the background color of the textbox when the error occurs. But i don't know how to bind the border-brush property dynamically with text-box when the error occur. I have shared the small portions of my code.
XAML code
<TextBlock
Margin="0 110 0 12"
Style="{StaticResource Label}"
Text="Name"
/>
<TextBox
Margin="0 0 300 0" MaxLength="20" Text="{x:Bind ViewModel.Name, Mode=TwoWay}"
/>
<TextBlock Style="{StaticResource ErrorStyle}" Text="{x:Bind ViewModel.IsNameValid, Mode=TwoWay}"/>
ViewModel
private string IsNameValid_;
public string IsNameValid
{
get => this.IsNameValid_;
set => SetProperty(ref this.IsNameValid_, value);
}
public async Task RegisterPatientInfo()
{
ValidationCheck(this.Name,);
}
ValidationMethod
public void ValidationCheck(string name, string kana, string patientNumber, string year, string month, string days)
{
//Name Validation
var regexNamePattern = #"^([a-zA-Z]){0,20}$";
Regex nameRegex = new Regex(regexNamePattern);
if (!nameRegex.IsMatch(name))
{
this.IsNameValid = "";
}
A common way is to use data-binding with ValueConverter to dynamically change the color of an UIelement.
Here is a simple demo that you could refer to.
ColorConverter:
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
SolidColorBrush brush = null;
string IsNameValid = value as string;
if (IsNameValid.Equals("123"))
{
brush = new SolidColorBrush(Colors.Red);
}
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Xaml:
<Page.Resources>
<local:ColorConverter x:Key="ColorConverter"/>
</Page.Resources>
<TextBox Background="{x:Bind ViewModel.IsNameValid,Converter={StaticResource ColorConverter}, Mode=TwoWay}"/>

File path to file name String converter not working

Using a wpf ListBox I'm trying to display a list of filename without displaying the full path (more convenient for user).
Data comes from an ObservableCollection which is filled using Dialog.
private ObservableCollection<string> _VidFileDisplay = new ObservableCollection<string>(new[] {""});
public ObservableCollection<string> VidFileDisplay
{
get { return _VidFileDisplay; }
set { _VidFileDisplay = value; }
}
In the end I want to select some items and get back the full file path. For this I have a converter :
public class PathToFilenameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//return Path.GetFileName(value.ToString());
string result = null;
if (value != null)
{
var path = value.ToString();
if (string.IsNullOrWhiteSpace(path) == false)
result = Path.GetFileName(path);
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
Which I bind to my listbox itemsource :
<ListBox x:Name="VideoFileList" Margin="0" Grid.Row="1" Grid.RowSpan="5" Template="{DynamicResource BaseListBoxControlStyle}" ItemContainerStyle="{DynamicResource BaseListBoxItemStyle}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemsSource="{Binding Path=DataContext.VidFileDisplay, Converter={StaticResource PathToFileName},ElementName=Ch_Parameters, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Path=SelectedVidNames,ElementName=Ch_Parameters, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
Without the converter, it is working fine (but of course this is the full path displayed in the listbox). With the converter I have one character per line... displaying this :
System.Collections.ObjectModel.ObservableCollection`1[System.String]
Where am I wrong ?
Thank you
In ItemsSource binding converter applies to the whole list and not to each item in the collection. If you want to apply your converter per item you need to do it ItemTemplate
<ListBox x:Name="VideoFileList" ItemsSource="{Binding Path=DataContext.VidFileDisplay, ElementName=Ch_Parameters}" ...>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter={StaticResource PathToFileName}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Alternate ListBox row colors

I'm trying to stylish this ListBox with alternate row colors:
<ListBox x:Name="listBox" Background="{x:Null}" SelectionChanged="listBox_SelectionChanged" >
<ListBox.Resources>
<local:AltBackgroundConverter x:Name="AltBackgroundConverter" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0" Height="50" Background="{Binding Converter={StaticResource AltBackgroundConverter}}" >
<TextBlock Text="{Binding Titulo}" Style="{StaticResource ListViewItemTextBlockStyle}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This is the AltBackgroundConverter.cs:
public class AltBackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (!(value is int)) return null;
int index = (int)value;
if (index % 2 == 0)
return Colors.White;
else
return Colors.Gray;
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Obviously, it's not working because I'm not achieving how to bind the row number to the converter. As you can see, I'm binding a entire ListBox Item.
I think the solution should be really simple, but I can't find it.
There are a couple of problems with your solution right now.
Your binding to your list items, while the converter needs an integer to figure out if an item is even or odd. Based on your screenshot you should change it so it looks like this:
Background="{Binding Numero, Converter={StaticResource AltBackgroundConverter}}"
Your converter returns a color, while the Background is a brush, so you could fix it like this:
public class AltBackgroundConverter : IValueConverter
{
private Brush whiteBrush = new SolidColorBrush(Colors.White);
private Brush grayBrush = new SolidColorBrush(Colors.Gray);
public object Convert(object value, Type targetType, object parameter, string language)
{
if (!(value is int)) return null;
int index = (int)value;
if (index % 2 == 0)
return whiteBrush;
else
return grayBrush;
}
// No need to implement converting back on a one-way binding
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Things should start working at this point, but your highlighting will only highlight the text instead of entire rows. This is how you can fix that up:
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem">
<Setter
Property="HorizontalContentAlignment"
Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>

WPF binding: Set Listbox Item text color based on property

I'm sure this is probably something basic in WPF but I'm new to XAML syntax I'm trying to wrap my head around it.
The Setup
I have a LogItem Type -- just a POCO:
public class LogItem
{
public string Message {get;set;}
public Color MessageColor {get;set;}
}
and a List of LogItem in my ViewModel:
private ObservableCollection<LogItem> _logItems;
public ObservableCollection<LogItem> LogItems
{
get { return _logItems; }
set
{
if (value != _logItems)
{
_logItems = value;
OnPropertyChanged("LogItems");
}
}
}
My viewmodel is bound to the view so that I can do the following:
<ListBox Grid.Row="0" Margin="0,10,0,0" Grid.ColumnSpan="3" Height="150" ItemsSource="{Binding LogItems}">
(Obviously I still have to set the display text binding, etc.)
The Question
Given that I have a Message and MessageColor property in LogItems, what is the correct XAML syntax to bind the color of the item text to the color I specify?
<ListBox Grid.Row="0" Margin="0,10,0,0" Grid.ColumnSpan="3" Height="150" ItemsSource="{Binding LogItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Message}" Foreground="{Binding MessageColor}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
TextBlock Foreground expects a Brush not a Color. Like a lot of things in WPF, There are lot's of ways to approch this. Here is a couple:
Change to MessageColor property in your viewModel to Brush
Brush MessageColor {get;set;}
Create a SolidColorBrush and bind it to your color
<TextBlock Text="{Binding Message}">
<TextBlock.Foreground>
<SolidColorBrush Color="{Binding MessageColor}"/>
</TextBlock.Foreground>
</TextBlock>
Create a ColorToBrushConverter
public class ColorToBrushConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null) return Brushes.Black; // Default color
Color color = (Color)value;
return new SolidColorBrush(color);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
In xaml, create the converter as static resource
<Window.Resources>
<local:ColorToBrushConverter x:Key="colorToBrushConverter"/>
</Window.Resources>
use it in the binding
<TextBlock Text="{Binding Message}" Foreground="{Binding MessageColor, Converter={StaticResource colorToBrushConverter}"/>
Good luck

How to get the selected index of MVVM-bound radiobuttons?

I have an ItemsControl which is bound and set to an observablecollection in my viewmodel:
<ItemsControl ItemsSource="{Binding AwaySelection}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding AwayText}" ></RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now, how to find out which one is clicked? I would like to bind the IsChecked value of each Radiobutton to a single variable in the viewmodel that returns an index to the collection. This would make it very easy for me to directly reference the selected item. Any ideas?
This is how I solved this problem. I wrote an EnumToBool converter for this, like
public class EnumToBoolConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value,
Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (parameter.Equals(value))
return true;
else
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return parameter;
}
#endregion
}
And I've the following enumeration
public enum CompanyTypes
{
Type1Comp,
Type2Comp,
Type3Comp
}
Now, in my Xaml, I'm passing the types as the converter parameter.
<Window x:Class="WpfTestRadioButtons.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTestRadioButtons"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<local:EnumToBoolConverter x:Key="EBConverter"/>
</Window.Resources>
<Grid>
<StackPanel>
<RadioButton IsChecked="{Binding Path=Type,
Converter={StaticResource EBConverter},
ConverterParameter={x:Static local:CompanyTypes.Type1Comp}}" Content="Type1"/>
<RadioButton IsChecked="{Binding Path=Type,
Converter={StaticResource EBConverter},
ConverterParameter={x:Static local:CompanyTypes.Type2Comp}}" Content="Type2"/>
</StackPanel>
</Grid>
</Window>
Now, in your view model, you should have a property (in this case Type), which is of that Enum type.
Like,
public CompanyTypes Type
{
get
{
return _type;
}
set
{
_type = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Type"));
}
}
In this example, you might have noticed that Radiobuttons are static. In your case, as you are listing the radio buttons inside an Item control, you need to bind your ConverterParameter of your RadioButton as well, to the correct type.
In the end, I put the radio buttons into a listview, and bind the isselected property of the listview to the radiobutton one.
link Forum post describing this technique
When use MVVM with radiobutton control exits a problem on method onToggle(), but you can create a radiobutton for that.
public class DataBounRadioButton: RadioButton
{
protected override void OnChecked(System.Windows.RoutedEventArgs e) {
}
protected override void OnToggle()
{
this.IsChecked = true;
}
}
Then add reference to control and Binding a property, in my case IsActive.
<controls:DataBounRadioButton
IsChecked="{Binding IsActive}"/>

Categories

Resources