Bind Visibility to ReactiveCommand CanExecute - wpf

I have several Tiles (TileLayoutControl Class) in my xaml (only shown 2 in this example) whose Visibility are binded to Boolean Properties and converted through BooleanToVisibilityConverter.
This works just fine. My question is
Can I bind the visibility to the Command instead so that I can remove the need of those several Boolean Properties?
Something like binding the Visibility to Command.CanExecute
If Yes, How can I achieve that? Any help will be really appreciated! Thanks.
<dxlc:Tile Command="{Binding Tile1Command}"
Visibility="{Binding Path=IsTile1Visible , Converter={StaticResource BooleanToVisibilityConverter}}"/>
<dxlc:Tile Command="{Binding Tile2Command}"
Visibility="{Binding Path=IsTile2Visible , Converter={StaticResource BooleanToVisibilityConverter}}"/>
ViewModel
private bool _isTile1Visible;
public bool IsTile1Visible
{
get { return _isTile1Visible; }
set { this.RaiseAndSetIfChanged(ref _isTile1Visible, value); }
}
public ReactiveCommand Tile1Command { get; private set; }
Tile1Command = new ReactiveCommand();
Tile1Command.Subscribe(p => PerformTile1Operation());

Yes, just use RxUI bindings:
<dxlc:Tile x:Name="Tile1" />
Then in your View constructor (make sure to implement IViewFor<Tile1ViewModel> to get this extension):
this.BindCommand(ViewModel, x => x.Tile1Command);
this.WhenAnyObservable(x => x.ViewModel.Tile1Command.CanExecuteObservable)
.BindTo(this, x => x.Tile1.Visibility);
You could also solve this in the ViewModel level, though that's not what I would do - in the ViewModel ctor:
Tile1Command = new ReactiveCommand(/* ... */);
Tile1Command
.Select(x => x ? Visibility.Visible : Visibility.Collapsed)
.ToProperty(this, x => x.Tile1Visibility, out tile1Visibility);

ReactiveCommand is an ICommand implementation that is simultaneously a RelayCommand implementation...
Assume that the ReactiveCommand has been declared like this...
public ReactiveCommand FileCommand { get; private set; }
...and has been instantiated in a View Model like this...
SomeText = "";
FileCommand = new ReactiveCommand(this.WhenAny(vm => vm.SomeText, s => !string.IsNullOrWhiteSpace(s.Value)));
FileCommand.Subscribe(param => MessageBox.Show("Processing"));
... which means if the property SomeText is empty, then the command cannot be executed, otherwise the command can be executed. And if the command is executed, a message box will get displayed.
If your objective is to simply eliminate the boolean IsTile1Visible, you can make a Xaml declaration like this...
<Button Content="File"
Command="{Binding FileCommand}"
Visibility="{Binding FileCommand, Converter={genericMvvm1:CommandToVisibilityConverter}}" />
where the visibility is bound to the same command and uses a value converter...
and the value converter looks like this...
public class CommandToVisibilityConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
ICommand iCommand = value as ICommand;
if (iCommand != null)
{
if (iCommand.CanExecute(parameter))
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
}
catch
{
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
The value converter simply dereferences the command into a basic ICommand and converts it into a visibility. Note that since this converter inherits from Markup Extension, there's no need to declare it as a static resource in the Xaml's object graph.
NOTE: the same functionality can be achieved by using 'code-behind' available in ReactiveUI, but the Xaml/ValueConverter appeals to developers who do not want their View Models to deal explicitly with the 'Visibility' property.

You could potentially do that, but it would require subclassing the command so that it also implements INotifyPropertyChanged, and the underlying condition would need to raise PropertyChange for the CanExecute property whenever it changes.
It won't work without that, as ICommand doesn't implement INotifyPropertyChanged - it uses CanExecuteChanged instead.
Note that you could simplify the property, however, by just handling it yourself in the constructor:
// In constructor:
Tile1Command = new ReactiveCommand();
Tile1Command.Subscribe(p => PerformTile1Operation());
IReactiveObject self = this as IReactiveObject;
Tile1Command.CanExecuteChanged += (o,e) => self.RaisePropertyChanged(new PropertyChangedEventArgs("IsTile1Visible"));
Then your property becomes:
// Use command directly here...
public bool IsTile1Visible
{
get { return Tile1Command.CanExecute; }
}

Related

How to implement INotifyPropertyChanged on indexed child in SL4?

My ViewModel class has a child property of type 'Messages' that has an indexed property, like:
public class ViewModel
{
// ...
public Messages Messages
{
get
{
if (_messages == null)
{
LoadMessagesAsync();
_messages = new Messages();
}
return _messages;
}
set
{
_messages = values;
PropertyChanged(new PropertyChangedArgs("Messages");
}
}
// ...
private void LoadMessagesAsync()
{
// Do the service call
Messages = theResult;
}
}
public class Messages
{
// ...
public String this[String name]
{
get { return _innerDictionary[name]; }
}
// ...
}
I don't think I need to fill in the rest of the gaps as it is all straight-forward.
The problem I am having is that the binding is not updating when I set the Messages property to a new object. Here is how I am referencing the property in XAML (with ViewModel as the DataContext):
<TextBlock Text="{Binding Messages[HelloWorld]}" />
It was my assumption that the binding would update when the PropertyChanged event was raised for the "Messages" property.
I've read elsewhere that my Messages class should raise a PropertyChanged event with either an empty string (""), "Item[]" or "Item["+name+"]" for the property name. However, since I am completely replacing the Messages object, this won't work as I never actually change the contents.
How do I make this work?
UPDATE
So I've done some digging into the behavior and into the BCL source code to see what's expected as a way to figure out how to make my code work. What I've learned is two-fold:
First, Silverlight data-binding is actually looking at the return object from the Messages property as the source of the binding. So raising PropertyChanged from ViewModel (sender is ViewModel) is not handled by the binding. I actually have to raise the event from the Messages class.
This is no different than using the following: Text={Binding Messages.HelloWorld}"
The reason that Myles' code work is that 'Data' returns 'this' so the binding is fooled into treating the parent class as the binding source.
That said, even if I make it so my child object raises the event, it still won't work. This is because the binding uses the System.Windows.IndexerListener as the binding target. In the SourcePropertyChanged method, the listener checks if the property name is "Item[]" but takes no action. The next statement delegates to the PropertyListener which checks the property name and only handles the event if it is equal to "Item[HelloWorld]".
So, unless I explicitly raise the event for each possible value within my collection, the UI will never update. This is disappointing because other articles and posts indicate that "Item[]" should work but looking at the source proves otherwise.
Nevertheless, I still hold out hope that there is a way to accomplish my goals.
OK the underlying problem here is that the Binding does not have a Path specified, therefore, the binding framework does not know which property name to look out for when handling PropertyChanged events. So I have fabricated a Path for the binding in order for change notification to work.
I have wrote the following code that proves the indexer binding is refreshed when the actual underlying dictionary changes:
ViewModel
public class BindingTestViewModel : AppViewModelBase, IBindingTestViewModel
{
private Dictionary<string, object> _data = new Dictionary<string, object>();
public BindingTestViewModel()
{
_data.Add("test","1");
_data.Add("test2", "21");
}
public object this[string index]
{
get
{
return _data[index];
}
set
{
_data[index] = value;
NotifyOfPropertyChange(() => Data);
}
}
public object Data
{
get
{
return this;
}
}
public void Refresh()
{
_data = new Dictionary<string, object>
{
{"test", "2"}, {"test2", "22"}
};
NotifyOfPropertyChange(() => Data);
}
}
My XAML:
<navigation:Page.Resources>
<Converters:IndexConverter x:Name="IndexConverter"></Converters:IndexConverter>
</navigation:Page.Resources>
<Grid x:Name="LayoutRoot">
<TextBox Height="23"
HorizontalAlignment="Left"
Margin="288,206,0,0"
Name="textBox1"
Text="{Binding Path=Data,Converter={StaticResource IndexConverter},ConverterParameter=test}"
VerticalAlignment="Top" Width="120" />
<Button x:Name="ReloadDict" Click="ReloadDict_Click" Width="50" Height="30" Content="Refresh" VerticalAlignment="Top"></Button>
</Grid>
The converter:
public class IndexConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var vm = value as BindingTestViewModel;
var index = parameter as string;
return vm[index];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I don't necessarily like answering my own questions but I have found a solution to my problem. I've been able to keep my original flow and address the idiosyncrocies of SL4 data-binding. And the code seems a bit cleaner, too.
What it boils down to is that I don't replace the child object anymore. That seems to be the key. Instead, I create a single instance and let that instance manage changing the internal list of items as needed. The child object notifies the parent when it has changed so the parent can raise the PropertyChanged event. The following is a brief example how I've gotten it to work:
public class ViewModel
{
// ...
public Messages Messages
{
get
{
if (_messages == null)
{
lock (_messagesLock)
{
if (_messages == null)
{
_messages = new Messages();
_messages.ListChanged += (s, e) =>
{
NotifyPropertyChanged("Messages");
};
}
}
}
return _messages;
}
}
}
public class Messages
{
// ...
public String this[String name]
{
get
{
if (_innerDictionary == null)
{
_innerDictionary = new Dictionary<String, String>();
LoadMessagesAsync();
}
return _innerDictionary[name];
}
}
// ...
private void LoadMessagesAsync()
{
// Do the service call
_innerDictionary = theResult;
NotifyListChanged();
}
// ...
public event EventHandler ListChanged;
}
For brevity, I've left out the obvious parts.

Silverlight Databinding Question

I have a TextBox ( TextBoxConsumer ) and i would like to enable a button in my UI when the length of the TextBox.Text is greater than 3,
i digged it down to
IsEnabled="{Binding
ElementName=TextBoxConsumer,
Path=Text.Length}"
for my button's IsEnabled Property but im not sure how to find the length and convert it to bool depending on the length of the text box how do i do it?
i would like to do it entirely in Xaml instead of code using Binding instead of code
I would prefer to use an IValueConverter class for this. I'll provide some quick code though its not exactly what you are looking for you should be able to tweak it.
In a cs file by itself:
using System;
using System.Globalization;
using System.Windows.Data;
public class IntCorrectAnswerToTrueFalseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (int)value > 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? 1 : 0;
}
}
In App.xaml, add this line to the ResourceDictionary:
<app:IntCorrectAnswerToTrueFalseConverter x:Key="IntCorrectAnswerToTrueFalseConverter" />
Then in the xaml of where you use it:
<CheckBox
x:Name="answerCheckBox"
IsChecked="{Binding Score, Converter={StaticResource IntCorrectAnswerToTrueFalseConverter}}"
Click="CheckBoxChecked"/>
I did something similar using a tutorial similar to this using the INotifyPropertyChanged interface. I assume you have a model you are using for binding to the UI. You have a string member (like TextBoxConsumerString) which binds to you textbox. Now you need to add a boolean like TextBoxConsumerEnabled which you will set inside of the setter of TextBoxConsumerString and call the notify changed method.
this.OnPropertyChanged( new PropertyChangedEventArgs( "TextBoxConsumerEnabled" ) );
Here is an example:
public class TextBoxConsumerModel : INotifyPropertyChanged
{
private string _textBoxConsumerString;
public event PropertyChangedEventHandler PropertyChanged;
public string TextBoxConsumerString
{
get
{
return _textBoxConsumerString;
}
set
{
if (_textBoxConsumerString == value)
return;
TextBoxConsumerEnabled = value != null && value.Length > 3;
_textBoxConsumerString = value;
OnPropertyChanged(new PropertyChangedEventArgs("TextBoxConsumerEnabled"));
}
}
public bool TextBoxConsumerEnabled { get; set; }
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
}
That should be it as far as the model goes. Now you just need to bind to the two model properties from the XAML.

Current Date in Silverlight XAML TextBlock

I am coming from Flex where you can do just about anything inside of curly braces. I am trying to get a TextBlock to display today's Date and Time without just coding it in C#. I have tried many different variations of the following with no luck.
TextBlock Text="{Source=Date, Path=Now, StringFormat='dd/MM/yyyy'}"
I know I could probably just set a property MyDate and bind to that but why can't I bind directly to the DateTime.Now property?
Binding in Silverlight requires a Source object or a Dependency object. From that source object you can bind to Properties (hence by definition you are binding to instance members) or Dependency Properties.
Since DateTime.Now is a static property you cannot bind to it in Silverlight directly, hence some code is needed. The next best thing is to use code to:-
ensure as much of what you need can be expressed in XAML
to do so in an as de-coupled manner as possible.
Hence we can analyse that we need two things.
Expose the static members of DateTime as instance properties of some object
Have some way to format the DateTime to a desirable output.
To handle the first item I would create a StaticSurrogate class, where I would create instance properties for the static properties that we need access to:-
public class StaticSurrogate
{
public DateTime Today { get { return DateTime.Today; } }
public DateTime Now { get { return DateTime.Now; } }
}
Now we need a way to format a Date time. A value converter is the right tool for this job, borrowing heavily from this Tim Heuer Blog :-
public class FormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (parameter != null)
{
string formatterString = parameter.ToString();
if (!String.IsNullOrEmpty(formatterString))
{
return String.Format(culture, String.Format("{{0:{0}}}", formatterString), value);
}
}
return (value ?? "").ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
With these two classes in place we can now do the rest in Xaml, first we need instances of these classes in our resources:-
<UserControl.Resources>
<local:StaticSurrogate x:Key="Static" />
<local:FormatConverter x:Key="Formatter" />
</UserControl.Resources>
Now we can wire up the TextBlock :-
<TextBlock Text="{Binding Today, Source={StaticResource Static},
Converter={StaticResource Formatter}, ConverterParameter='dd MMM yyy'}" />
Note that this approach has the following advantages:-
we do not need to add code to the UserControl on which the TextBlock is placed, nor do we have to fiddle around with any data context.
The Static resources could be placed in the App.Resources which would make the creation of the TextBlock entirely independent of having to add anything else to the UserControl.
The formatting used to display the date can be independently modified.
Access to additional static properties can easily be added to the StaticSurrogate class.
Even if you could declare DateTime.Now in Silverlight's XAML (since you can in WPF - http://soumya.wordpress.com/2010/02/12/wpf-simplified-part-11-xaml-tricks/), you have the issue that your time won't update. If you use a local timer that updates on the second you can ensure that your time will update as well.
public class LocalTimer : INotifyPropertyChanged
{
private DispatcherTimer timer;
public LocalTimer()
{
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1.0);
timer.Tick += new EventHandler(TimerCallback);
this.TimeFormat = "hh:mm:ss";
this.DateFormat = "dddd, MMMM dd";
}
private void TimerCallback(object sender, EventArgs e)
{
PropertyChanged(this, new PropertyChangedEventArgs("FormattedDate"));
PropertyChanged(this, new PropertyChangedEventArgs("FormattedTime"));
}
public bool Enabled
{
get { return this.timer.IsEnabled; }
set { if (value) this.timer.Start(); else this.timer.Stop(); }
}
public string FormattedDate { get { return DateTime.Now.ToString(this.DateFormat); } set {} }
public string FormattedTime { get { return DateTime.Now.ToString(this.TimeFormat); } set{} }
public string TimeFormat { get; set; }
public string DateFormat { get; set; }
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Declare an instance of this in xaml ala:
<local:LocalTimer x:Key="theTime" Enabled="True" />
and use the binding to ensure that your time is always reflected.
<TextBlock Text="{Binding Source={StaticResource theTime}, Path=FormattedDate, Mode=OneWay}" x:Name="TodaysDate" />
<TextBlock Text="{Binding Source={StaticResource theTime}, Path=FormattedTime, Mode=OneWay}" x:Name="CurrentTime" />
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Text="{Binding Source={x:Static sys:DateTime.Today}, StringFormat='Today is {0:dddd, MMMM dd}'}"

Silverlight TabControl bound to ObservableCollection<string> not updating when collection changed

Silverlight 3 app with a TabControl bound to an ObservableCollection using an IValueConverter. Initial the binding works (converter called) on app startup. Changes, Clear() or Add(), to the bound collection are not reflected in the TabControl... converter not called.
note: the bound ListBox reflects the changes to the bound collection while the TabControl does not.
Ideas?
/jhd
The XAML binding...
<UserControl.Resources>
<local:ViewModel x:Key="TheViewModel"/>
<local:TabConverter x:Key="TabConverter" />
</UserControl.Resources>
<StackPanel DataContext="{StaticResource TheViewModel}">
<ListBox ItemsSource="{Binding Classnames}" />
<controls:TabControl x:Name="TheTabControl"
ItemsSource="{Binding Classnames, Converter={StaticResource TabConverter}, ConverterParameter=SomeParameter}"/>
<Button Click="Button_Click" Content="Change ObservableCollection" />
</StackPanel>
The ViewModel...
namespace DatabindingSpike
{
public class ViewModel
{
private ObservableCollection<string> _classnames = new ObservableCollection<string>();
public ViewModel()
{
_classnames.Add("default 1 of 2");
_classnames.Add("default 2 of 2");
}
public ObservableCollection<string> Classnames
{
get { return _classnames; }
set { _classnames = value; }
}
}
}
The converter (for completeness)...
namespace DatabindingSpike
{
public class TabConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var source = value as ObservableCollection<string>;
if (source == null)
return null;
var param = parameter as string;
if (string.IsNullOrEmpty(param) || param != "SomeParameter")
throw new NotImplementedException("Null or unknow parameter pasased to the tab converter");
var tabItems = new List<TabItem>();
foreach (string classname in source)
{
var tabItem = new TabItem
{
Header = classname,
Content = new Button {Content = classname}
};
tabItems.Add(tabItem);
}
return tabItems;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Update 8/19
The concise answer is you have to implement INotifyPropertyChanged on the view model and notify listeners when the Property/Collection is changed.
Implement INotifyPropertyChanged on the ViewModel
* implement the interface INotifyPropertyChanged
* define the event (public event PropertyChangedEventHandler PropertyChanged)
* subscribe to the CollectionChanged event (Classnames.CollectionChanged += ...)
* fire the event for listeners
Best,
/jhd
ViewModel update per above... ValueConverter now called on all changes to the Property/Collection
public class ViewModel : INotifyPropertyChanged
{
private readonly ObservableCollection<string> _classnames = new ObservableCollection<string>();
public ViewModel()
{
Classnames.CollectionChanged += Classnames_CollectionChanged;
}
public event PropertyChangedEventHandler PropertyChanged;
private void Classnames_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Classnames");
}
private void NotifyPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
foreach (PropertyChangedEventHandler d in handler.GetInvocationList())
{
d(this, new PropertyChangedEventArgs(info));
}
}
}
public ObservableCollection<string> Classnames
{
get { return _classnames; }
}
}
The XAML binding...
<UserControl.Resources>
<local:ViewModel x:Key="TheViewModel"/>
<local:TabConverter x:Key="TabConverter" />
</UserControl.Resources>
<StackPanel DataContext="{StaticResource TheViewModel}">
<ListBox ItemsSource="{Binding Classnames}" />
<controls:TabControl x:Name="TheTabControl"
ItemsSource="{Binding Classnames, Converter={StaticResource TabConverter}, ConverterParameter={StaticResource TheViewModel}}"/>
<Button Click="Button_Click" Content="Change Classnames" />
</StackPanel>
The ValueConverter (basically unchanged
public class TabConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var source = value as ObservableCollection<string>;
if (source == null)
return null;
//also sorted out the binding syntax to pass the ViewModel as a parameter
var viewModel = parameter as ViewModel;
if (viewModel == null)
throw new ArgumentException("ConverterParameter must be ViewModel (e.g. ConverterParameter={StaticResource TheViewModel}");
var tabItems = new List<TabItem>();
foreach (string classname in source)
{
// real code dynamically loads controls by name
var tabItem = new TabItem
{
Header = "Tab " + classname,
Content = new Button {Content = "Content " + classname}
};
tabItems.Add(tabItem);
}
return tabItems;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I realize this is a slightly old question at this point, but I don't know that anyone has explained why you need to do the INotifyPropertyChanged on the bound property on your view model.
The ItemsControl itself needs to be bound to an ObservableCollection for the collection change events to cause the ItemsControl to re-evaluate. Your converter is returning a distinct List (or Observable) collection each time it is called rather than holding on to a single ObservableCollection and adding items to it. Therefore, these collections never have any of the collection changed events raised on them... they're always new, each time the binding is re-done.
Raising PropertyChanged forces the binding to be re-evaluated and re-runs your converter, returning a distinct collection and reflecting your changes.
I feel a better approach may be to do the conversion in your ViewModel rather than in a Converter. Expose an ObservableCollection of TabItem that you bind directly to and that you modify in place. The TabControl should then see changes made directly to your collection without the need to raise PropertyChanged and re-evaluate the entire binding.
[Edit - Added my approach]
ViewModel:
public class TabSampleViewModel
{
private ObservableCollection _tabItems = new ObservableCollection();
public TabSampleViewModel()
{
AddTabItem("Alpba");
AddTabItem("Beta");
}
public ObservableCollection<TabItem> TabItems
{
get
{
return _tabItems;
}
}
public void AddTabItem( string newTabItemName )
{
TabItem newTabItem = new TabItem();
newTabItem.Header = newTabItemName;
newTabItem.Content = newTabItemName;
TabItems.Add( newTabItem );
}
}
View:
<controls:TabControl ItemsSource="{Binding TabItems}"/>
Expose
public ObservableCollection<TabItem> Classnames
{
get { return _classnames; }
set { _classnames = value; }
}
If you debug the valueconverter you'll see it's not being called as often as you think it is.
The problem could be that your ValueConverter returns a List<TabItem> instead of an ObservableCollection<TabItem>. Try that one line change and see if it helps.

WPF binding ComboBox to enum (with a twist)

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))

Resources