I have a simple combo :
<ComboBox x:Name="testCombo" SelectedValue="{Binding State, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center" MinWidth="100">
<ComboBoxItem>OPEN</ComboBoxItem>
<ComboBoxItem>CLOSED</ComboBoxItem>
</ComboBox>
That state is just a string property with INotifyPropertyChanged implemented.
private string state;
public string State
{
get { return state; }
set
{
state = value;
OnPropertyChanged("State");
}
}
What i want to achieve is, when that State string property is initially set to "OPEN", when my window loads, the ComboBox to set it's initial item as "OPEN".
I also tried to attach a converter there:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ComboBoxItem cbi = new ComboBoxItem();
cbi.Content = value as string;
return cbi;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value as ComboBoxItem).Content;
}
With this, my State string property will be fine populated, but the ComboBox won't get to the desired initial state.
ComboBoxItem is not compared by content but by reference and the instance you create in converter is not the same instance displayed in ComboBox so they will never be equal. What you can do is set ItemsSource as list of strings and bind SelectedItem directly to string property without any converter
<ComboBox SelectedItem="{Binding State, Mode=TwoWay}" x:Name="testCombo">
<ComboBox.ItemsSource>
<x:Array Type="{x:Type sys:String}">
<sys:String>OPEN</sys:String>
<sys:String>CLOSED</sys:String>
</x:Array>
</ComboBox.ItemsSource>
</ComboBox>
you'll need to add sys namespace to your XAML as well
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Related
I have a property on my view model of type decimal?. NULL should be a valid value for this property, but when I erase the text from the DecimalUpDown control, a validation error occurs and the property is not given the value NULL (whatever it was previously remains).
The control is declared in xaml like:
<xctk:DecimalUpDown ValueChanged="UpDownBase_OnValueChanged" Text="{Binding ServiceSize}" Minimum="0" Grid.Column="4" Grid.Row="2" Margin="5" IsEnabled="{Binding IsEditable}"/>
It will bind correctly if I enter a number
But as soon as the number is erased a validation error occurs, and the value can't be set back to NULL (in this case the model still has "5" as the value for "ServiceSize").
Validation.GetHasError() returns true for this control. Can I remove the Validation Rules entirely somehow?
You can implement an IValueConverter to handle empty input.
public class DecimalUpDownValueConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
// handle input on a case-to-case basis
if(value == null)
{
// Do something
return 0;
}
else
{
return value;
}
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Do the conversion from model property to DecimalUpDownValue
return value;
}
}
On your view: (Assuming you added the DecimalUpDownValueConverter as a static resource)
<xctk:DecimalUpDown ValueChanged="UpDownBase_OnValueChanged" Text="{Binding ServiceSize, Converter = { StaticResource DecimalUpDownValueConverter }}" Minimum="0" Grid.Column="4" Grid.Row="2" Margin="5" IsEnabled="{Binding IsEditable}"/>
I'm trying to build a settings page to allow the user to choice which action to execute on item swipe, like the Outlook app.
To do this I created an enum containing the available actions, and I'm binding it to a ComboBox.
Everything works, the user can choose the action and his choice is saved correctly. The problem is that the ComboBox doesn't show the selected item when I navigate to the page, it shows it only after selection.
This means that if user changes selection then the ComboBox is updated, but the selected item is shown as blank upon navigation.
Here's my code:
(XAML)
<ComboBox x:Uid="LeftActionComboBox"
Grid.Row="0"
HorizontalAlignment="Stretch"
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay, Converter={StaticResource StringToSwipeActionTypesConverter}}"
ItemsSource="{Binding LeftSwipeActionType, Converter={StaticResource EnumToStringListConverter}}"/>
(VM Property)
public SwipeActionTypes LeftSwipeActionType
{
get { return _settings.LeftSwipeActionTypeProperty; }
set
{
_settings.LeftSwipeActionTypeProperty = value;
// RaisePropertyChanged causes a StackOverflow, but not using it is not the problem since the ComboBox is empty only before set
}
}
(Converter StringToSwipeActionTypesConverter, localization-ready)
// Returns localized string value for the Enum
public object Convert(object value, Type targetType, object parameter, string language)
{
var enumValue = (SwipeActionTypes) value;
switch (enumValue)
{
case SwipeActionTypes.Copy:
return App.ResourceLoader.GetString("CopySwipeActionName");
case SwipeActionTypes.Delete:
return App.ResourceLoader.GetString("DeleteSwipeActionName");
default:
throw new ArgumentOutOfRangeException();
}
}
// Parses the localized string into the enum value
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
var stringValue = (string) value;
if (stringValue.Equals(App.ResourceLoader.GetString("CopySwipeActionName")))
{
return SwipeActionTypes.Copy;
}
if (stringValue.Equals(App.ResourceLoader.GetString("DeleteSwipeActionName")))
{
return SwipeActionTypes.Delete;
}
return null;
}
(Converter EnumToStringListConverter)
public object Convert(object value, Type targetType, object parameter, string language)
{
var valueType = value.GetType();
return Enum.GetNames(valueType).ToList();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
Any idea on why this is failing?
The reason you are getting a StackOverflow exception is because every time you change LeftSwipeActionType property you are changing the ItemsSource of the ComboBox which changes the SelectedItem which fires INotifyPropertyChanged which changes the ItemsSource and so on and so on.
Once you stop using the same property for ItemsSource and SelectedItem then the correct initial selection will be set.
Rather than use a converter to create your ItemsSource you should just create is in your ViewModel
public MyViewModel(type enumType)
{
SourceForItems = Enum.GetValues(enumType);
}
public IEnumerable SourceForItems { get; private set; }
First of all, here is whats wrong with your approach:
You are binding your ItemsSource to the same property as the SelectedItem, even tough you are using a converter this can cause an infinite update circle - and you don't want that.
Generating the same static list of elements over and over again seems a bit wasteful. Instead of passing an instance of a type, lets just pass the type itself to the converter:
EnumToMembersConverter.cs
public class EnumToMembersConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return Enum.GetValues((Type)value).ToList();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return DependencyProperty.UnsetValue;
}
}
XAML
ItemsSource="{Binding Source={x:Type whateverNamespace:SwipeActionTypes}, Converter={StaticResource EnumToMembersConverter}}"
This will give you all Values of SwipeActionTypes, therefore you can bind it directly, without converting back again.
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay}"
There is nothing wrong with using a ComboBox for types other than string, so lets make this your base for further steps:
<ComboBox x:Uid="LeftActionComboBox"
Grid.Row="0"
HorizontalAlignment="Stretch"
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay}"
ItemsSource="{Binding Source={x:Type whateverNamespace:SwipeActionTypes}, Converter={StaticResource EnumToMembersConverter}}"/>
The reason you wrote all those converts is probably because the ComboBox showed strange values instead of readable strings. No worries, we already have your converter, you just need to invert it (Convert SwipeActionTypes to String) and apply it to a TextBox:
<ComboBox x:Uid="LeftActionComboBox"
Grid.Row="0"
HorizontalAlignment="Stretch"
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay}"
ItemsSource="{Binding Source={x:Type whateverNamespace:SwipeActionTypes}, Converter={StaticResource EnumToMembersConverter}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter = {StaticResource SwipeActionTypesStringConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Note, I didn't run this code so you might need to adjust the used namespaces accordingly
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}}"/>
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>
I bound my text box to property (only text box) and used converter which just return opposite bool value.
XAML
<TextBox Height="23" HorizontalAlignment="Left" Margin="13,41,0,0" Text="{Binding Path=CurrentProfile.profileName}" IsEnabled="{Binding Path=CurrentProfile.isPredefined, Converter={StaticResource PredefinedToControlEnabled}}" VerticalAlignment="Top" Width="247" Grid.ColumnSpan="2" TabIndex="1" />
Converter
public class PredefinedToControlEnabled: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var isPredefined = (bool)value;
return !isPredefined;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
When the property value isPredefined is true, the IsEnabled property of TextBox is set correctly to false. When the isPredefined is false, the converter returns false and all controls on GUI sets IsEnabled property to false, what means that application is not useable anymore (it works in background naturally). Why it is happening?
How does it look like:
Found,
VS automagically add line to Window properties
IsEnabled="{Binding Path=CurrentProfile.isPredefined}"
but the question still is, how?