I'm trying to use Prism and the MVVM pattern to develop an application. In my UI, I have a previous and next button defined. For use in a calling web services, I've defined an enum that will tell me the direction I need to traverse. So, in this instance, the buttons map directly to an enum value. The enum definition is very simple and is as follows:
namespace CodeExpert.Book.Helpers
{
public enum BookDirection { Previous = -1, NotSet = 0, Next = 1, }
}
I've defined my command and delegate in my ViewModel and assigned the propery correctly. The relevant code is:
public DelegateCommand PreviousNextCommand { get; set; }
public IndexEntriesViewModel(GlobalVariables globalVariable, IndexEntryOperations currentOperator)
{
//a bunch of initialization code.
InitializeCommands();
}
void InitializeCommands()
{
PreviousNextCommand =
new DelegateCommand(OnPreviousNextCommandExecute);
}
private void OnPreviousNextCommandExecute(BookDirection parameter)
{
//Code to process based on BookDirection
}
So, based on this config, I want to pass a BookDirection enum value to the CommandParameter. I can't, however, get the XAML right for this. Here's the XAML I've tried that seems the most correct to me:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="CodeExpert.Book.Views.Index"
d:DesignWidth="1024"
d:DesignHeight="768"
xmlns:helpers="clr-namespace:CodeExpert.Book.Helpers"
xmlns:command="clr-namespace:Microsoft.Practices.Composite.Presentation.Commands;assembly=Microsoft.Practices.Composite.Presentation"
xmlns:common="clr-namespace:System.Windows;assembly=System.Windows.Controls"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:input="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input">
<Button x:Name="ButtonPrevious"
HorizontalAlignment="Left"
Margin="2,1,0,1"
Width="25"
Content="<"
Grid.Column="1"
Grid.Row="1"
Height="20"
command:Click.Command="{Binding Path=CurrentIndexViewModel.PreviousNextCommand}">
<command:Click.CommandParameter>
<helpers:BookDirection.Previous />
</command:Click.CommandParameter>
</Button>
</UserControl>
I get no intellisense for the BookDirection for the enum and get an error at design & compile time saying The type 'BookDirection' does not contain adefinition for 'Previous'. Is there no way to pass that enum, or am I simply missing something? I have it working now by setting the parameter type to string instead of BookDirection, but then I have to parse text and the code smells. I've done some Google searches and the closest thing I've come to an answer is here - Passing an enum value as command parameter from XAML Unfortunately, Silverlight doesn't support the x:static binding extension, so I can't use the exact technique described in that answer.
Any help would be much appreciated.
Just pass command parameter like simple object type. Then you can use direct cast to your enum.
Binding enums are really a troublesome operation, even in WPF. But there seems to be an elegant solution to it, which is available at the Caliburn framework.
The solution, however, is not available in the framework code, but in it's LOB Samples. Look for the BindableEnum and BindableEnumCollection<T> classes on the Caliburn.Silverlight.ApplicationFramework project.
HTH.
First, I am not sure if you can pass an Enum directly from XAML in Silverlight. In anycase here is a little problem in Prism. See the Command is getting set before the CommandParameter, what happens when the command is getting set internally Prism calls UpdateEnabledState(), which internally calls CanExecute(object parameter) on your DelegateCommand passing it the CommandParameter ( which remember has not been set yet )
here is the basic code from DelegateCommand.cs in Prism
bool ICommand.CanExecute(object parameter)
{
return CanExecute((T)parameter);
}
since parameter is "null" at this point, the cast throws an exception. Here is how I got around this issue.
Your Enum.
namespace CodeExpert.Book.Helpers
{
public enum BookDirection { Previous = -1, NotSet = 0, Next = 1, }
}
here is my delegate declaration, note the use of object..rather than the Enum itself. I also expose properties from the ViewModel which will expose the 2 different directions.
public DelegateCommand<object> PreviousNextCommand { get; set; }
public BookDirection Previous { get { return BookDirection.Previous; }}
public BookDirection Next { get { return BookDirection.Next; }}
now in your OnPreviousNextCommandExecute make sure you receive an object and cast it back to the proper enum
private void OnPreviousNextCommandExecute(object parameter)
{
BookDirection direction = (BookDirection)parameter;
//Code to process based on BookDirection
}
and in XAML bind directly to the exposed BookDirection properties.
<Button Content="Next" Commands:Click.Command="{Binding PreviousNextCommand}" Commands:Click.CommandParameter="{Binding Next}" />
<Button Content="Previous" Commands:Click.Command="{Binding PreviousNextCommand}" Commands:Click.CommandParameter="{Binding Previous}" />
I am not sure about your binding situation as in my case I set my DataContext directly to the ViewModel. But this should work for you.
Sorry for my poor English and eloquence, hope this puts you on the right track.
I haven't tried it myself but you may have some success to use a ValueConverter from something like string to your enum. That was the impression I got from looking around for enums in Silverlight xaml.
Related
I am writing a WPF page to update data stored on an SQL server, and the way I am writing it requires a copy of the original data to be stored to create a WHERE clause.
So, what I am trying to do is have two copies of the data stored in variables on the page. One that remains as the original object and one that is to be used as the data binding context which are declared like so:
public Object Current { get; set; }
private readonly Object Start = new Object();
Which in my constructor are declared as:
public PageUpdate(Object source) {
InitializeComponent();
Current = source;
Start = source;
}
If my understanding of data binding is correct, the data binding should be entirely unable to see the Start object.
I have tried several approaches, including setting the data context with DataContext = Current;, using XAML to describe it in two different ways:
<TextBox x:Name="TextBox1" Text="{Binding Object.ObjectTextProperty, RelativeSource={RelativeSource FindAncestor AncestorType=Page}" />
<Page d:DataContext="{d:DesignInstance Type=Object}">
<TextBox x:Name="TextBox1" Text="{Binding ObjectTextProperty}" />
</Page>
And finally attempted to set each binding property in code-behind:
TextBox1.SetBinding(TextBox.TextProperty, new Binding("ObjectTextProperty")
{
BindingGroupName = "bindingGroup",
Source = Current,
Mode = BindingMode.TwoWay
});
And various combinations of the above.
Each time I've used this, I've tested by printing Start and Current to the debug console after editing the data from the front end and it always shows both objects as identical even though as far as I know, Start should never be altered by the data binding.
Am I missing something obvious here or should I just take a different approach?
It turned out that what I needed to do was implement the ICloneable interface on Object and then simply clone the object.
I added the following to my Object class:
public class Object : ICloneable
{
public object Clone()
{
return MemberWiseClone();
}
}
And then I made the following change to my page constructor:
public PageUpdate(Object source) {
InitializeComponent();
Current = source;
Start = (Object)source.Clone();
}
I hope this helps anyone else who has this problem.
Thanks to Steeeve for the solution.
Im trying to learn MVVM, but I'm having some trouble. Im new to xaml and c#.
What I have so far:
A person class, that define a person object: name, age and other info
A model class people, owns a private linkedlist (list of person), which also contains methods such as get, remove, add and do some calculations
a viewmodel class, doing casting/parsing/transforming between xaml behind code and the model.
A xaml behind code file mainWindow.xaml.cs, that listen to button click and such, and invoke methods from the viewModel class, and do some simple binding such as total.Content = objModelView.getTotal().
I didnt use INotifyPropertyChanged ObservableCollection, still trying to wrap my head around it. While my program does what I want I'm not sure how to structure it better.
Basically I have 2 main questions:
I see examples online where people store/initiate the list of items in viewmodel, shouldn't I keep the list in model instead, that should be where all the data be stored right?
Let's say I'm suppose to display all the items (in the list of the model class) onto a dataGrid. Right now in my program: mainWindow.xaml.cs will detect the button click, then it ask viewModel to store it in model, if no error then xaml behind code will do
people_dataGrid.Items.Add(new person { name = newName, age = newAge, address = newAdd }); Is this bad practice? Dont get how to use ObservableCollection here, can it somehow detect a change in the list of my model class, and then remove and add the rows to the datagrid?
I've been reading for the whole day but I'm struck here, hope I can get some direction
The model stores data, the view display it, the viewmodel is the bridge between the two.
That doesn't mean that the view have a direct access to model data because you don't always need to display all data in model layer. So we have a viewmodel layer that makes only useful information accessible.
The viewmodel is very useful when you want to display the same data multiple times but displayed differently: you don't have to replicate data, you only need to define twice what information you need from those data and how to display them.
What you're doing in your second question is using model in view : This is not MVVM. What you want to do is bind the ItemsSource DP of Datagrid to a list of PersonVM which will fetch information from Person.
You code could be structured like that:
public class Person {
public String Name {get; set;}
public int Age {get; set;}
}
public class PersonVM {
public PersonVM(Person model) {
_model = model;
}
private readonly Person _model;
internal Person Model {get {return _model;}}
public String Name {
get { return _model.Name; }
set { _model.Name = value; }
}
public int Age {
get {return _model.Age;}
set { _model.Name = value; }
}
}
//PersonV.xaml
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Age}"/>
</StackPanel>
public class People : ObservableCollection<Person> {
}
public class PeopleVM : ObservableCollection<PersonVM> {
public PeopleVM(People model) {
_model = model;
foreach(Person p in _model) {
Add(new PersonVM(p));
}
_model.CollectionChanged += CollectionChangedHandler;
}
private void CollectionChangedHandler(Object sender, NotifyCollectionChangedEventArgs args) {
switch (notifyCollectionChangedEventArgs.Action) {
case NotifyCollectionChangedAction.Add:
foreach(People p in args.NewItems) {
if(!this.Any(pvm => pvm.Model == p)) {
this.Add(new PersonVM(p));
}
}
break;
case NotifyCollectionChangedAction.Remove:
foreach(People p in args.OldItems) {
PersonVM pvm = this.FirstOrDefault(pe => pe.Model == p);
if(pvm != null) this.Remove(pvm);
}
break;
case NotifyCollectionChangedAction.Reset:
Clear();
break;
default:
break;
}
}
private readonly People _model;
}
//PeopleV.xaml
<ItemsControl ItemsSource={Binding}>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type PersonVM}">
<PersonV/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public class AppVM {
public AppVM() {
People p = ServiceLayer.LoadPeople(); //load people
People = new PeopleVM(p);
}
public PeopleVM People {get; set;};
}
//MainWindow.xaml
<Window ...
>
<Window.DataContext>
<local:AppVM/>
</Window.DataContext>
<PeopleV/>
</Window>
Answer to your post can be as long as one wishes to explain, perhaps a whole lengthy blog itself. I will try to just answer 2 of your specific questions here. I am not going to show the code for each sub-answer, you have to take it as home work. :)
I didnt use INotifyPropertyChanged ObservableCollection, still trying
to wrap my head around it. While my program does what I want I'm not
sure how to structure it better.
Why? If you don't use these magic sticks, it's better you write a WinForms app and not a WPF one. Forget everything and dive into these two. You must (no escape) understand and use them in MVVM/WPF. You can even defer reading my further answer for that matter.
I see examples online where people store/initiate the list of items in
viewmodel, shouldn't I keep the list in model instead, that should be
where all the data be stored right?
They are not wrong. Person class in model layer represents a real world entity and is must, however, I would not bother about having People class in model. It's just a collection that could easily be accommodated by ViewModel. I personally would prefer that way always.
Let's say I'm suppose to display all the items (in the list of the
model class) onto a dataGrid. Right now in my program:
mainWindow.xaml.cs will detect the button click, then it ask viewModel
to store it in model, if no error then xaml behind code will do
people_dataGrid.Items.Add(new person { name = newName, age = newAge,
address = newAdd }); Is this bad practice? Dont get how to use
ObservableCollection here, can it somehow detect a change in the list
of my model class, and then remove and add the rows to the datagrid?
That's not MVVM, trust me. At the maximum what you should be required to write in view code behind, is initializing view model and setting it as view's data context.
To handle view events (Button.Click for ex) you should use ICommand implementation that will be bound to Button.Command property in XAML. This way you decouple control's event handler from the code behind.
You need to have a ObservableCollection<Person> in your viewmodel which will be bound the DataGrid in view. So when click a button to add person, button's command object will update this collection and view will be refreshed automatically without having you to add it manually to data grid.
You aren't using MVVM at all. It sounds like you are using MVP, which is a completely different pattern.
Before you continue, you need to understand what MVVM was designed for, because its a highly complicated (seemlying over engineered pattern) with a huge number of abstractions just to write the ubiquitous To-Do list.
But you must do all of it, otherwise its not MVVM.
The Zen of MVVM
MVVM grew out of the realisation that writing good, bug-free, safe UI code is hard. Testing UI code is harder, and involves hiring human testers, that are slow and can get it wrong.
So the solution that they came up with was simple.
DON'T WRITE ANY CODE IN YOUR UI
Done.
Except, not. Now, your UI doesn't do anything, it just looks pretty. So they added an extra layer between the UI and the Program/Business Logic/Model, and they called it the ViewModel.
The job of the ViewModel was to tell the UI what to do. But the trick was to get it to tell the UI what to do, without the ViewModel knowing about the UI at all.
(MVP has a similar concept, but the Presenter DOES know about the UI)
By having the ViewModel not know about the UI at all, we can write clean code that can easily be debugged and tested using our usual bag of tricks. Such as unit testing, refactoring, static code analysis, dependency injection etc etc...
Times are good!
Except the View Model still doesn't know about the UI. So we know what the UI should look like, but the UI doesn't know, because no one is telling it...
So they added the Binding class. The binding class's job is to watch the ViewModel, and then update the UI whenever something changes in the ViewModel.
In the world of MVVM there have been two different approaches to how the Binding class works.
You have the WPF way of doing things, which is to implement an event that tells the Binding class that the ViewModel has been updated. This is fast and flexible, but really annoying to write.
And you have the AngularJS way of doing things, which is to poll the ViewModel for updates. This is ridiculously slow and buggy.
If you have been following me thus far, you will note that MVVM defines a flow of data from your Model to your View. A break in any part of this chain will make it "not work".
It all so COMPLICATED, why bother?
The only reason I've found that justifies MVVM's excessive complexity is that you can write a GUI which you can have 90% test coverage, as the view only covers a tiny part of your program.
If you think automated testing is overrated, then you shouldn't use MVVM.
I am also pretty new to WPF, C# and MVVM. I have read quite a fair bit for these two to three months, so maybe I'll share what I understood.
You seem to have the same question that I had a week or two ago. Data should not be stored in the model. Models are just data structures. Databases (or simulated alternatives like Lists) are the actual storages that store these data. Some people would simply keep them in ViewModel, while some would move them to something outside of MVVM (e.g. a "Repository" class).
You are doing it all wrong. In MVVM, Views do not interact with ViewModels in this manner - they interact via Commands and Bindings. The fact that your View is directly manipulating the list means that it's definitely wrong.
Example:
View (Window):
<Window.Resources>
<vm:MyViewModel x:Key="MyVM" />
</Window.Resources>
<Window.DataContext>
<StaticResourceExtension ResourceKey="MyVM" />
</Window.DataContext>
<DataGrid ItemsSource="{Binding PeopleList}" ..... >
<Button Command="{Binding StoreCommand}" .... >
ViewModel:
public static readonly DependencyProperty PeopleListProperty =
DependencyProperty.Register("PeopleList",
typeof(ObservableCollection<Person>),
typeof(ViewModel));
public ObservableCollection<Person> PeopleList
{
get
{
return GetValue(PeopleListProperty) as ObservableCollection<EmissionEntry>;
}
set
{
SetValue(PeopleListProperty, value);
}
}
private ICommand _storeCommand;
public ICommand StoreCommand
{
get
{
if (_storeCommand == null)
_storeCommand = new MyCommandImplementation();
return _storeCommand;
}
}
Person is your model class with name/age etc. The list is kept in ViewModel, unless you want to have a repository somewhere.
You probably haven't really read anything about ICommand, so I suggest reading it up first. It is too long to give a tutorial here, but you can ask questions after you have read up some.
I'm wondering how to bind a color property of an element to the ViewModel without leaking the view implementation (e.g. WPF) into the ViewModel and thus creating a dependency. For example, I have a TextBlock and I've bound its Foreground property like this:
<TextBlock Name="MyTextBlock" Foreground="{Binding Path=PropName}" />
Many sources like this, this, this, etc. use System.Windows.Media.Brush from within the ViewModel, like this:
public System.Windows.Media.Brush PropName
{
get
{
//assume presentation logic to determine correct color.
return System.Windows.Media.Brushes.Red;
}
}
I don't want my ViewModel to be tied to WPF (i.e. via System.Windows.Media.Brush) or any other presentation framework. Is there some way of doing it so that I can use a general or universal color type or even an RGB value and have it interpreted correctly in the XAML from the binding?
I would create an enumeration of colors.
e.g:
enum Colors {Red, Green, Yellow, Pink, Blue};
Then if you are developing with WPF, you could just create a ValueConverter, where you determine if enumeration is Red you can convert it the way you want it and return it for control. That way you can separate the UI logic with viewmodel. Viewmodel tells what color and UI handles the rest.
If you want custom colors, upper answer is a good way of handling it.
Just Bind string with the Hex into Background property of the element like this(Course you'll need a Notify implementation)
XAML
<Border Background="{Binding anycolor}" />
Code
public string anycolor { get; set; }
If you don't want to reference those namespaces maybe you can return a string and use a converter containing something like :
var color = (Color)ColorConverter.ConvertFromString("Red");
or RGB
var color = (SolidColorBrush)(new BrushConverter().ConvertFrom("#ffaacc"));
I might not understand the question very well though.
This is my new favorite creation (sorry about the VB):
Imports System.Windows.Data
Public Class MappingConverter
Implements IValueConverter
Public Property Mappings As New List(Of Mapping)
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.Convert
Return Mappings.Where(Function(m) m.Source = value.ToString).FirstOrDefault
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException
End Function
End Class
Public Class Mapping
Public Property Source As String
Public Property Target As Object
End Class
And used like this:
<UserControl.Resources>
<wpflib:MappingConverter x:Key="SelectionHighlightConverter">
<wpflib:MappingConverter.Mappings>
<wpflib:Mapping Source="True" Target="{x:Static Brushes.CornflowerBlue}" />
<wpflib:Mapping Source="False" Target="{x:Static Brushes.White}" />
</wpflib:MappingConverter.Mappings>
</wpflib:MappingConverter>
</UserControl.Resources>
When you use that converter on a binding of a boolean property, the True or False will converted to the specified color. It can actually map any string convertable value (int, string, boolean, etc) to any object you want.
I'm having an issue with C# using WPF.
Just being brief here.
The following code below collects names via Entity Framework into a list.
This is in my MainWindow.xaml.cs file.
public ObservableCollection<string> FruitInfo
{
get
{
using (var context = new Fruit())
{
ObservableCollection<string> fruits= new ObservableCollection<string>();
foreach (var item in context.Fruits.OrderBy(s => s.FruitName))
{
fruits.Add(item.FruitName);
}
return fruits;
}
}
}
In my MainWindow.xaml file, I have the following:
<GroupBox Grid.Row="0" Grid.Column="0" Margin="5" Header="Fruit Info" >
<ComboBox Margin="5" SelectedItem="{Binding FruitInfo}"/>
</GroupBox>
When running my project, I see that the Combo Box does not populate the fruits.
Any ideas why I'm not seeing this?
All thoughts appreciated
You should bind the ItemsSource of the ComboBox to your collection, and the SelectedItem to another string that will represent the user's selection.
First:
<GroupBox Grid.Row="0" Grid.Column="0" Margin="5" Header="Fruit Info" >
<ComboBox Margin="5" ItemsSource="{Binding FruitInfo}" SelectedItem="{Binding SelectedFruit}"/>
</GroupBox>
Second: Make a SelectedFruit in your ViewModel
public string SelectedFruit { get; set; }
Ok, I understand what your trying to do, even though I'm still not sure why you're trying to do it.
The idea of using using is that it creates the variable for you, and the disposes of it when you finish the block of code you're running.
Now, you're creating a variable in that block, and return it ... and then, the system tries to dispose of it. So your return collection must be implicitly convertible to System.IDisposable, which I doubt yours is.
Putting that aside, you should follow emedbo advice. You will bind the source to the collection, and have another property for the selected index (since you're using mvvm).
I wouldn't get the data like that inside a using inside a getter, since it feels like that data you're getting might be deleted, and if it's not, then the whole use of your using is a bit wrong.
Not to mention it's not very readable, and you should aim for readability in most cases.
I don't use the Entity Framework, but I think the pattern for the FruitInfo property is missing of an important piece.
The problem is that the binding mechanism does not realize about the new ObservableCollection, because it expect some "notification" way to be alerted. That is, you have several ways to solve your problem:
use a DependencyPropety instead of an ordinary property: every time you set the property the bound controls are also notified.
I'd recommend this solution: reliable and versatile.
implement the INotifyPropertyChanged interface in the class exposing the FruitInfo property (e.g. MainWindow), then fire a PropertyChanged event on any actual FruitInfo's value changing.
This way is also valuable, but it looks useless adding a thing already exposed in any DependencyObject-derived class. The INotifyPropertyChanged fits perfectly for the POCO classes (Plain-Old CLR-Objects).
give a name to the combobox, then set the ItemsSource property explicitly.
It works fine, but you'd lose the benefits of the data-context inheritance, especially within templates.
the pattern you used creates the collection in a "lazy" fashion: consider avoiding the lazy-way, and set the FruitInfo value before the combobox is created/bound.
Doable, but typically may be applied in a few cases. Also requires that you know for sure the sequence of the objects creation. Keep as latest way.
== UPDATE
Try to modify your code as follows:
private ObservableCollection<string> _fruits = new ObservableCollection<string>();
public ObservableCollection<string> FruitInfo
{
get
{
using (var context = new Fruit())
{
this._fruits.Clear();
foreach (var item in context.Fruits.OrderBy(s => s.FruitName))
{
this._fruits.Add(item.FruitName);
}
return this._fruits;
}
}
}
I have an enumeration that has values like
HomeRun, StolenBase, FirstBase, etc.
I want to display these values in a combobox, with a space inserted before the capital letters, so it will display as 'Home Run', 'Stolen Base', etc.
I already have code that can do the formatting for me, and I have added that code to the 'Convert' method of an IValueConverter implementation.
My question is, where do I need to use this converter (in the xaml) such that not only the dropdown, but also the displayed value, will have this formatting? Do I need to implement ConvertBack as well?
I am well aware of setting 'descriptions' for the enumeration and using the popular EnumToDescriptionConverter, but I'd rather stay away from that.
I'm not sure if there is a better way, but you can achieve what you want using an ItemTemplate
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentPresenter
Content="{Binding Converter={StaticResource baseballEnumConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
This will display the converted value in your ComboBox.
The SelectedValue of the ComboBox will still be the Enum value. You won't need to implement ConvertBack.
[updated] The key point of my answer is that the enum values are converted totally. I think this way is eaier than the coverting each enum value.[/updated]
Where do I need to use this converter (in the xaml) such that not only the dropdown, but also the displayed value, will have this
formatting?
At Binding ItemsSource of ComboBox(ItemsSource="{Binding Source={x:Null}, Converter={StaticResource converter}}"), Please check the following code.
Do I need to implement ConvertBack as well?
No, you don't., because at runtime you cannot modify the enumeration, and even though it can do, you cannot change ItemsSource of ComboBox in VIEW, which means Binding Mode is OneWay.
XAML
<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MyEnumConverter x:Key="converter"/>
</Window.Resources>
<StackPanel>
<ComboBox ItemsSource="{Binding Source={x:Null}, Converter={StaticResource converter}, Mode=OneWay}"></ComboBox>
</StackPanel>
</Window>
Code
public enum MyEnum
{
HomeRun, StolenBase, FirstBase
}
[ValueConversion(typeof(object), typeof(List<string>))]
public class MyEnumConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var names = Enum.GetNames(typeof (MyEnum)).ToArray();
//Add some code to support the thing you want to do(add blank in front of Capital...)
return names;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
If you want the selected value of the ComboBox to be converted back to an enum then you will need to implement ConvertBack.
I'd personally go with the description attribute pattern that you mentioned, because
the obvious questions have already been asked, and
You aren't limited to simply inserting spaces at uppercase letters - you can use whatever description you want.
But assuming you want to go with this pattern, you just need to write your converter correctly. I'd suggest something like this:
// Convert method
var formattedNames = new List<string>();
foreach (var value in Enum.GetValues(typeof(Things)))
{
// Format is a method to convert the enum value to the display string
var formattedName = Format(value);
formattedNames.Add(formattedName);
}
// return a list of strings that you can bind to
return formattedNames;
// ConvertBack method
// Unformat is a method to revert the display string back to the enum value
var value = Unformat(formattedValue);
return Enum.Parse(typeof(Things), value);
You could also create a simple class to hold both the display value and the enum, and then set the DisplayPath property on the combo box appropriately
class DisplayEnum
{
public string DisplayValue { get;set; }
public MyEnum ActualValue { get;set; }
}
<ComboBox DisplayMemberPath=DisplayValue ...
Edit
I realise that this won't work because the ConvertBack is attempting to convert a string to an enum, but the actual binding set is a List<string>. I'll leave it here because it is a start in the right direction.
I believe you'd need two converters
to convert the enum type into a set of enum values, and
to convert an enum value to a string. This second converter should implement the ConvertBack method.
As I pointed out, if you don't implement ConvertBack then you won't be able to bind the SelectedValue back to your enum Property on your ViewModel.
You will need to make a dictionary or some other lookup structure that maps the Enum value to the string representation.
There is an hint that you can use as a start :
http://geekswithblogs.net/jawad/archive/2005/06/24/EnumDropDown.aspx
I developped my own enum binding helpers starting with that idea.