UWP subclassed ComboBox control in C# for enum - Display - combobox

I've developed a subclassed ComboBox control in C#, UWP, for enum type properties.
It works great! Almost all the time (... types).
Problem surfaced when the type of the enum was Windows.UI.Text.FontStyle.
The item selection still works right, but what it displays is not the .ToString() values, but Windows.Foundation.IReference`1<Windows.UI.Text.FontStyle> for each item.
When I debug, everything is the same and fine as far as my code is concerned.
My control works by a DependencyProperty called SelectedItemEnum - SelectedItemEnumProperty, which' type is object.
And by this binded concrete enum value it sets the ItemsSource:
ItemsSource = Enum.GetValues(SelectedItemEnum.GetType()).Cast<Enum>().ToList();
(And I handle the SelectionChanged event (inside the control) to set the value, but that part always works well.)

I have now been trying this for an hour or so, but I am unable to figure out why this happens and I would definitely be interested to see the real reason behind this.. Apparently there is something about the FontStyle enum, that causes it to get represented as nullable (IReference seems to be "equivalent" to nullable in .NET world).
To solve your problem however, you can build a custom ValueConverter, that will convert the value to string before displaying.
First create a ToStringConverter:
public class ToStringConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, string language )
{
var stringValue = value.ToString();
return stringValue;
}
public object ConvertBack( object value, Type targetType, object parameter, string language )
{
throw new NotImplementedException();
}
}
Now add is as a resource to your page or to the app itself:
<Page.Resources>
<local:ToStringConverter x:Key="ToStringConverter" />
</Page.Resources>
You can then use it with the combo box as follows:
<local:EnumComboBox x:Name="EnumComboBox">
<local:EnumComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource ToStringConverter}}" />
</DataTemplate>
</local:EnumComboBox.ItemTemplate>
</local:EnumComboBox>
This will correctly display the value of the enum. You can see it and try it out in here on my GitHub, along with the sample app I have used to try to figure this out.
I will keep trying to find the reason however, as it does really interest me :-) .

Related

How to resolve/control property name collision in XAML binding?

Interview Question
Phrased as:
If you have a property name collision, how would you specify the exact property to bind to in a Binding path expression (in XAML)?
I never faced this (property name collision) problem in any binding so far. With some reading I realized that this is possible in case I am binding to a overridden property because then I have two instances of this property (virtual in base, and overriden in derived) as far as resolution using Reflection is concerned. Which is what used by XAML.
Could there be any other case where XAML might face a property name collision?
Is there some support in API to handle/control that? (Instead of of course avoiding a collision)
Thanks for your interest.
Sounds like a complete nonsense to me. Unless they wanted to talk about bindings, using 'disjointed' sources like PriorityBinding and MultiBinding.
Frankly speaking I don't think overwritten properties can be involved into the matter as this is so much out of scope, you could equaly point out explicit interface implementations and many other things, which are clearly outside of WPF domain.
The best way I can think would be to use a ValueConverter. I don't think this really answers the question though since they're asking in a binding path expression, which I haven't seen to be possible. I'm not particularly fond of doing it this way because it feels like a hack, but it works at least for one way binding. Here's an example of how you might do it:
XAML:
<StackPanel Name="stack">
<StackPanel.Resources>
<loc:OverriddenMyPropertyConverter x:Key="BaseMyProperty"/>
</StackPanel.Resources>
<TextBox Text="{Binding Path=MyProperty}"/>
<TextBox Text="{Binding Mode=OneWay, Converter={StaticResource BaseMyProperty}}"/>
</StackPanel>
The DataContext of the StackPanel is an instance of MyClass. The first TextBox is bound to the MyClass.MyProperty property, and the second TextBox will be bound to the MyBaseClass.MyProperty property. Two way binding would be a bit more complex since the object actually being bound to the second TextBox is the MyClass object and not the MyProperty object.
Code:
class MyClass : MyBaseClass
{
string myProperty = "overridden";
public new string MyProperty
{
get { return myProperty; }
set { myProperty = value; }
}
}
class MyBaseClass
{
string baseProperty = "base";
public string MyProperty
{
get { return baseProperty; }
set { baseProperty = value; }
}
}
class OverriddenMyPropertyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value as MyBaseClass).MyProperty;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

WPF Image Source binding with StringFormat

I'm new to WPF and MVVM (started this week experimenting with it) and trying to bind image resources at runtime. The items I'm trying to display contain an enumerate property that indicates the type or state of the item:
public class TraceEvent
{
/// <summary>
/// Gets or sets the type of the event.
/// </summary>
/// <value>The type of the event.</value>
public TraceEventType EventType { get; set; }
}
As far as I known the Source attribute of Image has a value converter that takes strings and returns Uri objects.
<Image Source="{Binding Path=EventType, StringFormat={}/AssemblyName;component/Images/{0}Icon.ico}" />
So why doesn't the above work? If I enter the uri directly (without binding) the image is shown perfectly. In fact, if I do the binding in a TextBlock and use the result of that value in the Image also shown without problem:
<TextBlock Visibility="Collapsed" Name="bindingFix" Text="{Binding Path=EventType, StringFormat={}/AssemblyName;component/Images/{0}Icon.ico}"/>
<Image Source="{Binding ElementName=bindingFix, Path=Text}" />
I'm pretty sure I'm doing something terrible wrong for such an obvious thing to do with images.
Thanks.
StringFormat is only used if the target property is actually a string - the Image.Source property is a Uri so the binding engine won't apply the StringFormat.
One alternative is to use a Value Converter. Either write a general StringFormatConverter that takes the string format in the ConverterParameter, or a more specific ImageSourceConverter e.g.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return string.Format( "/Images/{0}Icon.ico", value );
}
Note that if your images live in the same assembly as they are used, then you shouldn't need to specify the assembly name in the URI and the above syntax should work.
i'm not sure about this one but it seems that you are passing the image's source property a string where it expects a uri. so, you have to convert your string into a uri object

WPF+MVVM: How to display VM via resource dictionary

At the moment I am creating new instance of both View and ViewModel and assign view.DataContext = vm in the application in the handler of 'Startup' application event
In the http://msdn.microsoft.com/en-us/magazine/dd419663.aspx in the "Applying a View to a ViewModel" chapter I've read that it would be a good idea to bind ViewModel object to view via resources.
Am I correctly understand that using suggested approach I should:
Create a resource in the "MainPage" that will have a "DataTemplate" section for each pair of View/ViewModel;
Bind instance of the ViewModel object to the MainPage?
Am I right?
P.S. Actually, I've tried to do that but got few issues and want to know if I am going by the proper way. If no, please point me how that should be done in the right way.
How this technique works is that instead of finding and creating views directly let wpf find the view through data templates. so when you have the following in your application resources. This drives the UI based on what ViewModel you want to display and don't have to worry about coding up the view.
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<ui:MyView />
</DataTemplate>
Note: vm: and ui: are just xml namespaces declared in the top element of the resource file.
you can then just create a generic window that will 'find' the view via a ContentControl
<Window ...>
<ContentControl Content="{Binding}" />
</Window>
var window = new Window();
window.DataContext = new MyViewModel();
window.Show();
This will display the window embedding MyView as the content of the window. Provided you have your bindings set in your view pointing to properties in your viewmodel the wire up succeed. No need to 'new' up a view. The main window can be reused simply by reassigning a different view model to the data context.
Also if you let us know what specific issues you are having we will be able to provide a more specific answer if the above is not what you are looking for.
HTH
I used to do a key/value pair for all of my ViewModel/View like aqwert suggests, but once you get a couple dozen,or more than one :), ViewModels it starts getting pretty tedious and prone to typos.
I personally like an IValueConverter doing the work for me and using Convention for the location of the View.
For example let's say I have my view models in namespace MyApp.ViewModels
and all of my Views in namespace MyApp.Views
and I have a suffix of ViewModel behind all of my VMs and a suffix of View behind all of my Views
All I have to do is:
1) Have all of my ViewModels inherit from a base class ViewModelBase
2) Put this in my application resource dictionary
<m:ViewModelConverter x:Key="ViewModelConverter"/>
<DataTemplate DataType="{x:Type vm:ViewModelBase}">
<ContentControl Content="{Binding Converter={StaticResource ViewModelConverter}}"/>
</DataTemplate>
3) Create my converter, the following is just an example, you can modify to meet your convention.
public class ViewModelConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
Type ViewModelType = value.GetType();
string ViewNameSpace = ViewModelType.Namespace.Replace("ViewModel", "View");
string ClassName = ViewModelType.Name.Replace("Model", string.Empty);
Type ViewType = Type.GetType(string.Format("{0}.{1}", ViewNameSpace, ClassName));
if (ViewType != null)
return Activator.CreateInstance(ViewType);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
The above will try and find the view, if it doesn't it will just return the ViewModel that it was trying to convert (which WPF will just call .ToString() on)
You don't have to worry about actual wiring of ViewModel to the View's DataContext because WPF does that automatically.
And then I'm done. I don't have to touch my resource file any more. :)

Why can't I select a null value in a ComboBox?

In WPF, it seems to be impossible to select (with the mouse) a "null" value from a ComboBox. Edit To clarify, this is .NET 3.5 SP1.
Here's some code to show what I mean. First, the C# declarations:
public class Foo
{
public Bar Bar { get; set; }
}
public class Bar
{
public string Name { get; set; }
}
Next, my Window1 XAML:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ComboBox x:Name="bars"
DisplayMemberPath="Name"
Height="21"
SelectedItem="{Binding Bar}"
/>
</StackPanel>
</Window>
And lastly, my Window1 class:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
bars.ItemsSource = new ObservableCollection<Bar>
{
null,
new Bar { Name = "Hello" },
new Bar { Name = "World" }
};
this.DataContext = new Foo();
}
}
With me? I have a ComboBox whose items are bound to a list of Bar instances, one of which is null. I have bound the window to an instance of Foo, and the ComboBox is displaying the value of its Bar property.
When I run this app, the ComboBox starts with an empty display because Foo.Bar is null by default. That's fine. If I use the mouse to drop the ComboBox down and select the "Hello" item, that works too. But then if I try to re-select the empty item at the top of the list, the ComboBox closes and returns to its previous value of "Hello"!
Selecting the null value with the arrow keys works as expected, and setting it programatically works too. It's only selecting with a mouse that doesn't work.
I know an easy workaround is to have an instance of Bar that represents null and run it through an IValueConverter, but can someone explain why selecting null with the mouse doesn't work in WPF's ComboBox?
Well I recently ran into the same problem with null value for ComboBox.
I've solved it by using two converters:
For ItemsSource property: it replaces null values in the collection by any value passed inside converter's parameter:
class EnumerableNullReplaceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var collection = (IEnumerable)value;
return
collection
.Cast<object>()
.Select(x => x ?? parameter)
.ToArray();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
For SelectedValue property: this one does the same but for the single value and in two ways:
class NullReplaceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value ?? parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.Equals(parameter) ? null : value;
}
}
Example of use:
<ComboBox
ItemsSource="{Binding MyValues, Converter={StaticResource EnumerableNullReplaceConverter}, ConverterParameter='(Empty)'}"
SelectedValue="{Binding SelectedMyValue, Converter={StaticResource NullReplaceConverter}, ConverterParameter='(Empty)'}"
/>
Result:
Note:
If you bind to ObservableCollection then you will lose change notifications. Also you don't want to have more than one null value in the collection.
The null "item" is not being selected by the keyboard at all - rather the previous item is being unselected and no subsequent item is (able to be) selected. This is why, after "selecting" the null item with the keyboard, you are thereafter unable to re-select the previously selected item ("Hello") - except via the mouse!
In short, you can neither select nor deselect a null item in a ComboBox. When you think you are doing so, you are rather deselecting or selecting the previous or a new item.
This can perhaps best be seen by adding a background to the items in the ComboBox. You will notice the colored background in the ComboBox when you select "Hello", but when you deselect it via the keyboard, the background color disappears. We know this is not the null item, because the null item actually has the background color when we drop the list down via the mouse!
The following XAML, modified from that in the original question, will put a LightBlue background behind the items so you can see this behavior.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ComboBox x:Name="bars" Height="21" SelectedItem="{Binding Bar}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid Background="LightBlue" Width="200" Height="20">
<TextBlock Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Window>
If you want further validation, you can handle the SelectionChanged event on the ComboBox and see that "selecting the null item" actually gives an empty array of AddedItems in its SelectionChangedEventArgs, and "deselecting the null item by selecting 'Hello' with the mouse" gives an empty array of RemovedItems.
I got a new solution for this question. "USING Mahapps"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
<ComboBox x:Name="bars" **controls:TextBoxHelper.ClearTextButton="True"**
DisplayMemberPath="Name"
Height="21"
SelectedItem="{Binding Bar}"/>
You can use the close button to clear the content.
Thanks.
I know this answer isn't what you asked for (an explanation of why it doesn't work with the mouse), but I think the premise is flawed:
From my perspective as a programmer and user (not .NET), selecting a null value is a bad thing. "null" is supposed to be the absence of a value, not something you select.
If you need the ability explicitly not to select something, I would suggest either the work-around you mentioned ("-", "n.a." or "none" as a value), or better
wrap the combobox with a checkbox that can be unchecked to disable the combobox. This strikes me as the cleanest design both from a user's perspective and programmatically.
I spent one day to find a solution about this problem of selecting a null value in combobox and finally, yeah finally I found a solution in an article written at this url:
http://remyblok.tweakblogs.net/blog/7237/wpf-combo-box-with-empty-item-using-net-4-dynamic-objects.html
public class ComboBoxEmptyItemConverter : IValueConverter
{
/// <summary>
/// this object is the empty item in the combobox. A dynamic object that
/// returns null for all property request.
/// </summary>
private class EmptyItem : DynamicObject
{
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
// just set the result to null and return true
result = null;
return true;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// assume that the value at least inherits from IEnumerable
// otherwise we cannot use it.
IEnumerable container = value as IEnumerable;
if (container != null)
{
// everything inherits from object, so we can safely create a generic IEnumerable
IEnumerable<object> genericContainer = container.OfType<object>();
// create an array with a single EmptyItem object that serves to show en empty line
IEnumerable<object> emptyItem = new object[] { new EmptyItem() };
// use Linq to concatenate the two enumerable
return emptyItem.Concat(genericContainer);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
<ComboBox ItemsSource="{Binding TestObjectCollection, Converter={StaticResource ComboBoxEmptyItemConverter}}"
SelectedValue="{Binding SelectedID}"
SelectedValuePath="ID"
DisplayMemberPath="Name" />
this might not address your answer completely, but hopefully its a hit in the right direction:
Have you installed SP1?
From Scott Gu's Blog:
NET 3.5 SP1 includes several data binding and editing improvements to
WPF. These include:
StringFormat support within {{ Binding }} expressions to enable easy
formatting of bound values
New alternating rows support within controls derived
from ItemsControl, which makes
it easier to set alternating properties on rows (for example: alternating background colors)
Better handling and conversion support for null values
in editable controls Item-level
validation that applies validation rules to an entire bound item
MultiSelector support to handle multi-selection and bulk
editing scenarios
IEditableCollectionView support to interface data controls
to data sources and enable editing/adding/removing items in a transactional way
Performance improvements when binding to IEnumerable data
sources
Sorry if I wasted your time and this was not even close..but I think the problem is inherited from:
constraints of the strongly typed dataset
NullValueDataSet Explained here
But now the SP1 for .Net 3.5 should have addressed this issue..
I had the same kind of problem we did some work around like adding a value property to the collection item like this :
public class Bar
{
public string Name { get; set; }
public Bar Value
{
get { return String.IsNullOrEmpty(Name) ? null : this; } // you can define here your criteria for being null
}
}
Then while adding items instead of null I use the same object :
comboBox1.ItemsSource= new ObservableCollection<Bar>
{
new Bar(),
new Bar { Name = "Hello" },
new Bar { Name = "World" }
};
And instead of selecteditem I bind it to selectedvalue :
<ComboBox Height="23" Margin="25,40,133,0" DisplayMemberPath="Name"
SelectedValuePath="Value"
SelectedValue="{Binding Bar}"
Name="comboBox1" VerticalAlignment="Top" />
I know It is not a complete solution, just one workaround I use
Try Binding.FallbackValue
From 6 Things I Bet You Didn't Know About Data Binding in WPF
ComboBox needs a DataTemplate to display the item no matter how simple it is.
DataTemplate works like this: get a value from instance.[path], e.g.
bar1.Car.Color
So it cannot get a value from
null.Car.Color
It will throw a null reference exception. So, the null instance will not be displayed. But the the Color - if it is a reference type - is allowed to be null because there will be no exception in this case.
Just a guess, but I think it sounds reasonable.
Assume combobox is using "ListCollectionView" (lcv as its instance) as its item collection, which it should be.
If you are a programmer, what you gonna do?
I will respons to both Keyboard and Mouse.
Once I get Keyboard input, I use
lcv.MoveCurrentToNext();
or
lcv.MoveCurrentToPrevious();
So, sure keyboard works well.
Then I am working on respons Mouse inputs. And it comes the problem.
I want to listen 'MouseClick' event of my item. But probably, my Item doesn't generated, it is just a placeholder. So when user click on this placeholder, I get nothing.
If I get the event successfully, what's next. I will invoke
lcv.MoveCurrentTo(selectedItem);
the "selectedItem" which would be null is not an acceptable parameter here I think.
Anyway, it's just guessing. I don't have time to debug into it though I am able to. I have a bunch of defects to fix. Good Luck. :)

What's the simplest way to display NULL values as "NULL" with WPF Data Binding?

I have this legacy database for which I'm building a custom viewer using Linq to Sql.
Now some of the fields in a table can have the value NULL.
Using normal databinding in a DataTemplate (typed for the Class generated by the ORM Designer)
<TextBlock Text="{Binding Path=columnX}"/>
If columnX has value NULL, nothing is displayed. (It seems the to be the WPF convention) I'd like to display "NULL" instead if the value is NULL. (equivalent to column_value ?? "NULL")
I could use a converter as in
<TextBlock Text="{Binding Path=columnX, Converter={StaticResource nullValueConverter}}"/>
Converter class
class NullValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return "NULL";
...
But this seems like too much work. Also this logic would need to be duplicated in existing non-trivial converters..
Is there a quick-hit way to accomplish this?
The binding class has a property called TargetNullValue that can be used to substitute something else if the binding returns a NULL value. Your example becomes:-
<TextBlock Text="{Binding Path=columnX, TargetNullValue=My Substitute Text}"/>
There is another property of the Binding class that is also useful called FallbackValue. This would be the substitute value to use if the binding expression cannot be resolved (i.e. not found), e.g for when the path you use (columnX in your example) is not a member of the data context or source.
Update(Gishu): Requires .NET Framework 3.5 SP1 a 53MB download. Without it, the above code won't compile. TargetNullValue is a new addition to the Binding class.
Right click on the DBML file, click View code. Add a partial class for the table you want to work with. Add a property returning value ?? null or something like that. The trick is that LINQ to SQL declares the classes as partial, so you can easily extend them.
I don't think there's a cleaner way to do it. You could possibly use a DataTrigger in a style template to pick up on null values, but data triggers can be a bit "funny" when using NULLs so you'd likely need a converter anyway.

Resources