I have ItemsControls with items binded from CollectionViewSource.
<ItemsControl ItemsSource="{Binding Source={StaticResource VisibleFlagsImageSourcePathView}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<r:RibbonRadioButton SmallImageSource="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And another control outsite:
<TextBox Text="{Binding Path=SelectedCountryCode" />
What I am trying to accomplish is whenever I change the value of the TextBox I want the corresponding RibbonRadioButton property IsChecked set to true or false.
What you need to do is create a ViewModel with two properties.
class MyViewModel
{
// Bind this to TextBox
public String SelectedCountryCode { get; set; }
// Bind this to ItemsControl
public ObservableCollection<Object> VisibleFlagsImageSourcePath { get; set; }
}
// Note I have omitted implementation of `INotifyPropertyChanged`. But, you will need to implement it.
And monitor the SelectedCountryCode, and whenever it changes, change appropriate value in VisibleFlagsImageSourcePath collection.
Radio buttons represent enumerated values. A text box in this case would represent an open value. What you seem to want is a set of open values as well as a pre-set selection of enumerated values. The control that best represents this is a combo box.
If you decide to continue with the radio button/text box approach, you can adapt the method people use to bind radio buttons to an enumerated value, except use a string field/string field type converter instead of an enum field/enum field type converter.
See this answer for how to bind to enums: How to bind RadioButtons to an enum?
To adapt this to strings, simply make a class called KnownStringToBooleanConverter (note that this is an identical implementation to EnumToBooleanConverter):
public class KnownStringToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(true) ? parameter : Binding.DoNothing;
}
}
Also create a type with your known strings (similar to how you would create an enum):
public static class KnownCountryCodes
{
// Note: I'm guessing at these codes...
public const string England = "EN";
public const string Japan = "JP";
}
Then bind to this in a similar way:
<RadioButton IsChecked="{Binding Path=SelectedCountryCode, Converter={StaticResource KnownStringToBooleanConverter}, ConverterParameter={x:Static local:KnownCountryCodes.England}}" />
<RadioButton IsChecked="{Binding Path=SelectedCountryCode, Converter={StaticResource KnownStringToBooleanConverter}, ConverterParameter={x:Static local:KnownCountryCodes.Japan}}" />
If you want all your controls to cross-populate, then you'll need to implement INotifyPropertyChanged in your view model:
public class MyViewModel : INotifyPropertyChanged
{
// Bind this to TextBox and radio buttons. Populate the radio buttons manually
public string SelectedCountryCode
{
get
{
return selectedCountryCode;
}
set
{
selectedCountryCode = value;
RaiseNotifyPropertyChanged("SelectedCountryCode");
}
}
/* Todo: Implement NotifyPropertyChanged and RaiseNotifyPropertyChanged here */
private string selectedCountryCode;
}
When a custom value (that isn't in the list) is entered, the radio buttons will all dim. When you type in a value that is from the list, the corresponding radio button will light up. When you select a correct radio button, the value will be changed in the text box.
This View/ViewModel stuff is called MVVM.
See: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
Related
I have an application with a TreeView control that is built using a set of data types representing different levels in the hierarchy and an accompanying set of HierarchicalDataTemplates. What I want to do now is set appropriate AutomationProperties.Name values on the tree items.
Normally, I would use TreeView.ItemContainerStyle to bind the accessible name, but this is rather limited, as it requires I use a binding path that works for all types.
In this case, however, I would much rather be able to control the accessible name independently for each type. For example, it may be useful to include Id in some layers, but not in others.
I could probably live with using the displayed text, but while I can easily use a RelativeSource binding in TreeView.ItemContainerStyle to get at the TreeViewItem, the Path needed to ultimately reach the TextBlock.Text value in the templated item from there eludes me.
I have also tried using HierarchicalDataTemplate.ItemContainerStyle, but that only applies to child items. Even further, when I tried to define it on each template, only BazItems were properly set, even though I would have expected BarItems to work as well.
I put together a minimal example to illustrate the issue. The item types are as follows:
public sealed class BazItem
{
public BazItem(int id, string name)
{
Id = id;
Name = name ?? throw new ArgumentNullException(nameof(name));
}
public int Id { get; }
public string Name { get; }
}
public sealed class BarItem
{
public BarItem(int id, string display)
{
Id = id;
Display = display ?? throw new ArgumentNullException(nameof(display));
Bazs = new ObservableCollection<BazItem>();
}
public int Id { get; }
public string Display { get; }
public ObservableCollection<BazItem> Bazs { get; }
}
public sealed class FooItem
{
public FooItem(int id, string name)
{
Id = id;
Name = name ?? throw new ArgumentNullException(nameof(name));
Bars = new ObservableCollection<BarItem>();
}
public int Id { get; }
public string Name { get; }
public ObservableCollection<BarItem> Bars { get; }
}
The corresponding templates are as follows:
<HierarchicalDataTemplate DataType="{x:Type local:BazItem}">
<TextBlock Text="{Binding Name, StringFormat='baz: {0}'}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:BarItem}"
ItemsSource="{Binding Bazs}">
<TextBlock Text="{Binding Display, StringFormat='bar: {0}'}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:FooItem}"
ItemsSource="{Binding Bars}">
<TextBlock Text="{Binding Name, StringFormat='foo: {0}'}"/>
</HierarchicalDataTemplate>
Finally, the tree view in the view is as follows:
<TreeView ItemsSource="{Binding Foos}"/>
where Foos is an ObservableCollection<FooItem> property on the underlying view.
The solution I came up with for now (pending a better answer) is to change the TreeView.ItemContainerStyle to use a value converter against the tree item object rather than a property on the object:
TreeView element:
<TreeView ItemsSource="{Binding ElementName=View, Path=Foos}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="AutomationProperties.Name" Value="{Binding Converter={StaticResource AccessibleConverter}}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Converter:
[ValueConversion(typeof(object), typeof(string), ParameterType = typeof(Type))]
public sealed class AccessibleTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch (value)
{
case FooItem foo:
return $"foo: {foo.Name}";
case BarItem bar:
return $"bar: {bar.Display}";
case BazItem baz:
return $"baz: {baz.Name}";
default:
return Binding.DoNothing;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This works, but it's less-than-ideal for several reasons:
it duplicates the format strings (though I could pull them out into a shared resource location)
the value converter has to first convert object to the appropriate type before it can come up with the appropriate string format
adding a new tree item type requires I touch both the templates as well as the value converter
I came along the same issue and the way I solved it was by overriding the ToString() method of the class of the object i was binding the TreeViewItem.
For example, in your case the type of item you bind to TreeViewItem is of type BazItem, go to BazItem class and:
public override string ToString()
{
// This is what the Windows Narrator will use now
return "Name of Baz Item";
}
I have two object: UserDto and RoleDto. User has a property which is the RoleDto.
In my viewmodel I have the following:
public UserDto User
{
get { return _user; }
set
{
if (_user == value) return;
_user = value;
User.PropertyChanged += UserPropertyChanged;
OnPropertyChanged("User");
}
}
private UserDto _user;
public IEnumerable<RoleDto> Roles { get; set; } //I load all available roles in here
In the view, I want to select the role that the user belongs. This is how I define the combobox in the view:
<ComboBox Grid.Row="3" Grid.Column="1" Margin="5" ItemsSource="{Binding Roles}" SelectedItem="{Binding User.Role, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Description" />
If I try to create a new user and select a role from the combobox, it is correctly binded to the user. The problem is that when I load a user that already exists, the role is not displayed in the combobox (even the user has a role defined).
Any help please?
Thanks in advance
This is because the reference of RoleDTO that your UserDTO has, does not match any of the RoleDTOs in Roles collection which you set as ItemsSource of ComboBox.
Better define a property on your ViewModel like
public RoleDTO SelectedRole
{
get { return Roles.FirstOrDefault(role => role.Role == User.RoleDto.Role); }
set { User.RoleDto = value; OnPropertyChanged("SelectedRole"); }
}
and set it as SelectedItem of you combobox
ItemsSource="{Binding Roles}" SelectedItem="{Binding SelectedRole, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Description" />
In my opinion the second option on this page is the easiest way.
https://rachel53461.wordpress.com/2011/08/20/comboboxs-selecteditem-not-displaying/
You can override the equals property on your object so that it returns true if the items have the same data. Then when the combo box box goes to check to make sure your item is in the selection it will find a match.
The other way to solve this problem is using Converter on Binding. when you use binding to bind SelectedItem, WPF will check the reference of SelectedItem against all objects inside ItemsSource property and of course if there was no match, SelectedItem will be empty. using Converter you can tell WPF that how it should match SelectedItem.
In this case you just need find SelectedItem among ItemsSource and return it to Binding. so follow these steps:
1- Create a class and implement IValueConverter. It has two methods: Convert and ConvertBack
2- for Convert method do something like this:
public class MySelecteItemBindingConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var mySelectedItem = value as MySelectedItemType;
var myItemsSource = parameter as List<MySelectedItemType>;
var matchedItem = myItemsSource.FirstOrDefault(i=>i.Id == mySelectedItem.Id);
return matchedItem;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
// Do just like Convert method
}
}
3- Use this Converter on your Binding like this:
var myBinding = new Binding("YourBindingPath");
myBinding.Converter = new MySelectedItemBindingConverter();
myBinding.ConverterParameter = myItemsSource; //this is List<MySelectedItemType> in this example
myCombo.SetBinding(ComboBox.SelectedItemProperty, myBinding);
Note: if you want to do binding from XAML you can not pass ConverterParameter like this, instead you should create a static list and use that as ItemsSource or use MultiBinding to pass your ConverterParameter using a trick. here there is a good and simple explanation about it: Binding ConverterParameter
I saw many posts on how to bind a boolean value to a radio button. But my scenario is that I need to bind it to a radio button and read the selection from the user.
That is no option should be selected intially.
If I bind it to a boolean, since boolean cant be null it shows the default value selected in radio button.
If I use nullable boolean, I still defaults to false when trying to use the converter.
I cant use oneway mode in xaml as I need to check if the radio button selection was made which I do using the bounded variable.
Ant pointers on how to achieve this?
You can bind a nullable boolean to your radio buttons, but you need to do it through a converter.
First declare your variable:
private bool? answer;
public bool? Answer
{
get { return answer; }
set
{
answer = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Answer"));
}
}
and initialise it to null.
Then in the XAML:
<Window.Resources>
<local:BooleanConverter x:Key="BooleanConverter"/>
</Window.Resources>
<StackPanel Grid.Row="1">
<RadioButton Content="I Agree"
IsChecked="{Binding Answer,
Converter={StaticResource BooleanConverter},
ConverterParameter='true', Mode=TwoWay}" />
<RadioButton Content="I Disagree"
IsChecked="{Binding Answer,
Converter={StaticResource BooleanConverter},
ConverterParameter='false', Mode=TwoWay}" />
</StackPanel>
And finally your converter:
class BooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var test = (bool?)value;
var result = bool.Parse((string)parameter);
if (test == result)
{
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var result = bool.Parse((string)parameter);
return result;
}
}
This binds the radio button to Answer and states that the first one will only be checked when the value is true and the second one will be checked when the answer is false. You can then check that Answer is not null before letting the user proceed.
This may not be the best way of doing it, but you could use something similar to this:
checkbox like radiobutton wpf c#
and just have two controls.
I appreciate it's not the best, but reading your answer it gives you what you want (generally).
I have a portion of a Window that should display one of several UserControls. Each UserControl presents the same data, only in a different format, arrangement, and style. The particular UserControl that will be presented in this section of the Window should be determined by a single setting that is stored in the ViewModel of the Window.
How can I make it so that the program end user can change the UserControl that is displayed in the Window at run-time?
I figured it out. In my ViewModel, I have a UserControl property called SelectedUC, and another property, called Style, that is an enum type, which enumerates the different UserControls that I am using. In the set part of the Style property I have OnPropertyChanged("SelectedUC"); The get part of the SelectedUC property has a switch-case statement that sets the field of the SelectedUC to a new instance of the corresponding type of UserControl and passes the ViewModel (this) as a parameter.
private MyStyleEnum _style = MyStyleEnum.OneStyle;
public MyStyleEnum Style
{
get { return _style; }
set
{
if (value != _style)
{
_style = value;
OnPropertyChanged("Style");
OnPropertyChanged("SelectedUC");
}
}
}
private UserControl _selectedUC;
public UserControl SelectedUC
{
get
{
switch (Style)
{
case MyStyleEnum.OneStyle:
_selectedUC = new ucOneControl(this);
break;
case MyStyleEnum.AnotherStyle:
_selectedUC = new ucAnotherControl(this);
break;
}
return _selectedUC;
}
set { _selectedUC = value; }
}
In my MainView's xaml, I have a ContentPresenter with the Content property bound to the SelectedUC property in the ViewModel.
<ContentPresenter Content="{Binding SelectedUC}" />
In my SettingsView's xaml, I have a group of RadioButtons that are all bound to the Style property and use a Converter and ConverterParameter.
<Window x:Class="MyProject.View.SettingsView"
xmlns:cv="clr-namespace:MyProject.Converters"
xmlns:vm="clr-namespace:MyProject.ViewModel">
<Window.Resources>
<cv:EnumToBoolConverter x:Key="EBConverter"/>
</Window.Resources>
<RadioButton Content="One" IsChecked="{Binding Path=Style, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ResourceKey=EBConverter}, ConverterParameter={x:Static Member=vm:MyStyleEnum.SingleLine}}"/>
</Window>
EnumToBoolConverter.cs:
public class EnumToBoolConverter : IValueConverter
{
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;
}
}
One (quick but not necessarily best) way is to add a ContentControl to your window
<ContentControl Name="cc" />
Then set the content of it however you like. Eg. set it in code-behind
cc.Content = new UserControl1();
Well the problem is that I have this enum, BUT I don't want the combobox to show the values of the enum. This is the enum:
public enum Mode
{
[Description("Display active only")]
Active,
[Description("Display selected only")]
Selected,
[Description("Display active and selected")]
ActiveAndSelected
}
So in the ComboBox instead of displaying Active, Selected or ActiveAndSelected, I want to display the DescriptionProperty for each value of the enum. I do have an extension method called GetDescription() for the enum:
public static string GetDescription(this Enum enumObj)
{
FieldInfo fieldInfo =
enumObj.GetType().GetField(enumObj.ToString());
object[] attribArray = fieldInfo.GetCustomAttributes(false);
if (attribArray.Length == 0)
{
return enumObj.ToString();
}
else
{
DescriptionAttribute attrib =
attribArray[0] as DescriptionAttribute;
return attrib.Description;
}
}
So is there a way I can bind the enum to the ComboBox AND show it's content with the GetDescription extension method?
Thanks!
I would suggest a DataTemplate and a ValueConverter. That will let you customize the way it's displayed, but you would still be able to read the combobox's SelectedItem property and get the actual enum value.
ValueConverters require a lot of boilerplate code, but there's nothing too complicated here. First you create the ValueConverter class:
public class ModeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
return ((Mode) value).GetDescription();
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
Since you're only converting enum values to strings (for display), you don't need ConvertBack -- that's just for two-way binding scenarios.
Then you put an instance of the ValueConverter into your resources, with something like this:
<Window ... xmlns:WpfApplication1="clr-namespace:WpfApplication1">
<Window.Resources>
<WpfApplication1:ModeConverter x:Key="modeConverter"/>
</Window.Resources>
....
</Window>
Then you're ready to give the ComboBox a DisplayTemplate that formats its items using the ModeConverter:
<ComboBox Name="comboBox" ...>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource modeConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
To test this, I threw in a Label too, that would show me the actual SelectedItem value, and it did indeed show that SelectedItem is the enum instead of the display text, which is what I would want:
<Label Content="{Binding ElementName=comboBox, Path=SelectedItem}"/>
I like the way you think. But GetCustomAttributes uses reflection. What is that going to do to your performance?
Check out this post:
WPF - Displaying enums in ComboBox control
http://www.infosysblogs.com/microsoft/2008/09/wpf_displaying_enums_in_combob.html
This is how I am doing it with MVVM. On my model I would have defined my enum:
public enum VelocityUnitOfMeasure
{
[Description("Miles per Hour")]
MilesPerHour,
[Description("Kilometers per Hour")]
KilometersPerHour
}
On my ViewModel I expose a property that provides possible selections as string as well as a property to get/set the model's value. This is useful if we don't want to use every enum value in the type:
//UI Helper
public IEnumerable<string> VelocityUnitOfMeasureSelections
{
get
{
var units = new []
{
VelocityUnitOfMeasure.MilesPerHour.Description(),
VelocityUnitOfMeasure.KilometersPerHour.Description()
};
return units;
}
}
//VM property
public VelocityUnitOfMeasure UnitOfMeasure
{
get { return model.UnitOfMeasure; }
set { model.UnitOfMeasure = value; }
}
Furthermore, I use a generic EnumDescriptionCoverter:
public class EnumDescriptionConverter : IValueConverter
{
//From Binding Source
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is Enum)) throw new ArgumentException("Value is not an Enum");
return (value as Enum).Description();
}
//From Binding Target
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is string)) throw new ArgumentException("Value is not a string");
foreach(var item in Enum.GetValues(targetType))
{
var asString = (item as Enum).Description();
if (asString == (string) value)
{
return item;
}
}
throw new ArgumentException("Unable to match string to Enum description");
}
}
And finally, with the view I can do the following:
<Window.Resources>
<ValueConverters:EnumDescriptionConverter x:Key="enumDescriptionConverter" />
</Window.Resources>
...
<ComboBox SelectedItem="{Binding UnitOfMeasure, Converter={StaticResource enumDescriptionConverter}}"
ItemsSource="{Binding VelocityUnitOfMeasureSelections, Mode=OneWay}" />
I suggest you use a markup extension I had already posted here, with just a little modification :
[MarkupExtensionReturnType(typeof(IEnumerable))]
public class EnumValuesExtension : MarkupExtension
{
public EnumValuesExtension()
{
}
public EnumValuesExtension(Type enumType)
{
this.EnumType = enumType;
}
[ConstructorArgument("enumType")]
public Type EnumType { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (this.EnumType == null)
throw new ArgumentException("The enum type is not set");
return Enum.GetValues(this.EnumType).Select(o => GetDescription(o));
}
}
You can then use it like that :
<ComboBox ItemsSource="{local:EnumValues local:Mode}"/>
EDIT: the method I suggested will bind to a list of string, which is not desirable since we want the SelectedItem to be of type Mode. It would be better to remove the .Select(...) part, and use a binding with a custom converter in the ItemTemplate.
Questions of using reflection and attributes aside, there are a few ways you could do this, but I think the best way is to just create a little view model class that wraps the enumeration value:
public class ModeViewModel : ViewModel
{
private readonly Mode _mode;
public ModeViewModel(Mode mode)
{
...
}
public Mode Mode
{
get { ... }
}
public string Description
{
get { return _mode.GetDescription(); }
}
}
Alternatively, you could look into using ObjectDataProvider.
I've done it like this :
<ComboBox x:Name="CurrencyCodeComboBox" Grid.Column="4" DisplayMemberPath="." HorizontalAlignment="Left" Height="22" Margin="11,6.2,0,10.2" VerticalAlignment="Center" Width="81" Grid.Row="1" SelectedValue="{Binding currencyCode}" >
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
in code I set itemSource :
CurrencyCodeComboBox.ItemsSource = [Enum].GetValues(GetType(currencyCode))