I create a VM based on MVVM light toolkit.
In VM, there is a simple ICommand(RelayCommand)
private RelayCommand _myCommand = null;
public RelayCommand MyCommand
{
get
{
if (_myCommand == null) //set break point here for debug
{
_myCommand = new RelayCommand(() =>
{
try
{
//....
}
catch (Exception ex)
{
// notify user if there is any error
//....
}
}
, () => true);
}
return _myCommand;
}
}
then in xaml, just bind this Command property to a button like:
<Button Grid.Column="1" x:Name="Test" Content="Test" Margin="2,0,2,0" Command="{Binding Path=MyCommand}" />
Then run the app, and click on the button, there is no response at all. No error.
VM is working fine. The data has been loaded to a datagrid before I click on the Test button.
If debug the app and put break point, the point is never reached.
How to resolve this problem?
Add a setter to your MyCommand property.
As always, check the Output window for any data binding errors when the XAML is rendered.
Also, try adding a test value converter and putting a breakpoint in the convert method to see if data binding is even being executed on that command. If the breakpoint isn't hit, you know you have a problem in your XAML. If the breakpoint is hit, take a look at the value to see if the data context is correct.
<UserControl.Resources>
<ResourceDictionary>
<TestConverter x:Key="TestConverter" />
</ResourceDictionary>
<Button Grid.Column="1" x:Name="Test" Content="Test" Margin="2,0,2,0" Command="{Binding Path=MyCommand, Converter={StaticResource TestConverter}}" />
</UserControl>
Test value converter - very useful for debugging data binding issues.
public class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("TestConverter.Convert(value := {0}, targetType := {1}, parameter := {2}, culture := {3})",
value, targetType, parameter, culture);
return value; // put break point here to test data binding
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("TestConverter.ConvertBack(value := {0}, targetType := {1}, parameter := {2}, culture := {3})",
value, targetType, parameter, culture);
return value;
}
}
Works on my machine :)
Seriously, I made a simple project, created a ViewModel, pasted in your code, and it worked. I am guessing you are dealing with some other issue.
Here is my C# code.
Here is my XAML code.
Time to evangelize a bit
This ViewModel code reeks. You might consider using some sort MVVM framework or helpers. If you look at ViewModelSupport, for instance, you can write your ViewModel like this:
public class MyViewModel : ViewModelBase
{
public void Execute_MyCommand()
{
// Your execution code here
}
}
Then, you avoid all that messy plumbing. Just think about it :)
the code looks fine. so you just have to check the output window for databinding errors. maybe you did not set the datacontext of the view correct. btw you should add your breakpoint in the try-catch of the command.
1) Make sure you're returning true from the relay command's CanExecute delegate. (I see you are doing this but good to double check).
2) Is the button inside a ListBox, DataGrid or DataForm?
For a ListBox or DataGrid:
If so you need to modify your binding expression to refer to the VM DataContext as opposed to the databound item. See this answer.
For a DataForm :
More tricky, but look at this question.
Related
We are looking for a way to make a textbox replace certain characters as a person types in it. Note, we are focusing on the control itself, not bindings, viewmodels, etc. For the sake of this question, assume there is a window with a textbox sitting in the middle of it and nothing else. No data, no viewmodel, no bindings, etc.
I've updated the question because it seems all the answers below keep focusing on bindings, dependency properties, coersion, etc. While I appreciate the suggestions, as I stated above, our control is not a bound control so they aren't applicable.
Now while I could explain the reasons for that, that would make this post about five times longer as it's actually a complex and advanced use-case, but that has nothing to do with the question itself, which is why I've simplified our scenario down to focus on the specific question we're trying to resolve, which is about a textbox control, or possibly a subclass of one replacing characters as you type.
Hope that makes more sense now.
The best way to accomplish this is using the TextChanged event:
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
var tb = (TextBox)sender;
using (tb.DeclareChangeBlock())
{
foreach (var c in e.Changes)
{
if (c.AddedLength == 0) continue;
tb.Select(c.Offset, c.AddedLength);
if (tb.SelectedText.Contains(' '))
{
tb.SelectedText = tb.SelectedText.Replace(' ', '_');
}
tb.Select(c.Offset + c.AddedLength, 0);
}
}
}
This has several advantages:
You don't go through the entire string every time, just the replaced part
It behaves well with the undo manager and with pasting text
You can easily encapsulate it into an attached property which can be applied to any text box
You can use an IValueConverter. It involves quite a bit of code, but it's the preferred way if you ever want to go the MVVM path, or just do things the WPF way.
First, create a class that implements IValueConverter
public class IllegalCharactersToUnderscoreConverter
: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var stringValue = value as String;
if(stringValue == null)
return value;
return RemoveIllegalCharacters(stringValue);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
Import the namespace containing your ValueConverter
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
Create an instance of your converter in Window.Resources
<Window.Resources>
<local:IllegalCharactersToUnderscoreConverter x:Key="IllegalCharactersToUnderscore" />
</Window.Resources>
Use the converter in your binding expression. UpdateSourceTrigger=PropertyChanged is required for the text to be converted as you type.
<TextBox Text="{Binding MyText,
Converter={StaticResource IllegalCharactersToUnderscore},
UpdateSourceTrigger=PropertyChanged}"/>
You could subclass TextBox you can just override the Metadata of the TextBox Text property
public class FilteredTextBox : TextBox
{
public FilteredTextBox()
{
TextBox.TextProperty.OverrideMetadata(typeof(FilteredTextBox), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceMyTextValue, true, UpdateSourceTrigger.PropertyChanged));
}
private static object CoerceMyTextValue(DependencyObject d, object baseValue)
{
if (baseValue != null)
{
var userEnteredString = baseValue.ToString();
return userEnteredString.Replace(' ', '_');
}
return baseValue;
}
}
And you dont need to use bindings at all, this will just update the internal TextBox TextProperty as you type
<Window x:Class="WpfApplication13.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="428" Width="738" Name="UI"
xmlns:my="clr-namespace:WpfApplication13" >
<StackPanel>
<my:FilteredTextBox />
</StackPanel>
</Window>
I tried to achieve this thing by handling this in my ViewModel property that is bind to TextBox itself like below and it works. In this example I replace '!' with underscore. Here i just put the replacement logic in property setter and if there is a change I replaced the text and Raised the propertychange async.
private string text;
public string Text
{
get { return text; }
set
{
text = value;
if (text.Contains("!"))
{
text = text.Replace('!', '_');
Dispatcher.BeginInvoke((Action) (() => RaisePropertyChange("Text")));
}
RaisePropertyChange("Text");
}
}
and the textbox binding is
<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>
How can I select the currentItem in a menuitem collection. Like one would do with a listbox. I tried wrapping the collection in a collectionViewSource, However that brought be no such luck.
Thanks in advance.
The MenuBase, which ContextMenu and Menu derive from, inherits ItemsControl, which does not include the concept of SelectedItem. That's something that ListBox adds.
You do, however, have the ItemsControl.ItemTemplate. Which is awesome.
One option would be to make your ItemTemplate a ToggleButton. This gives you a couple of things. Inherently, ToggleButtons can look like they're selected using their IsChecked property. Second, they have a Command property which you can bind to a command in your ViewModel.
So, if you have something along the lines of:
<Menu ItemsSource="{Binding ThingsToBindTo}">
<Menu.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.Resources>
<conv:BindingProxy x:Key="proxy" Data="{Binding}" />
</Grid.Resources>
<ToggleButton Content="{Binding NameOrLabel}" CommandParameter="{Binding}" Command="{Binding Path=DataContext.SelectThingCommand, RelativeSource={RelativeSource AncestorType=Menu}}" >
<ToggleButton.IsChecked>
<Binding Mode="OneWay" Path="DataContext.SelectedThing" RelativeSource={RelativeSource AncestorType=Menu}">
<Binding.Converter>
<conv:ComparisonConverter CompareTo="{Binding Source={StaticResource proxy}, Path=Data}" />
</Binding.Converter>
</Binding>
</ToggleButton.IsChecked>
</ToggleButton>
</Grid>
</DataTemplate>
</Menu.ItemTemplate>
</Menu>
So this is a little complicated.
As per normal, you're binding to a list of items. ThingsToBindTo should be whatever your list is. Then you start defining your template. NameOrLabel is whatever property you want to appear on your toggle button. The command parameter is binding to the data item that the template is wrapping around by using nothing more than "{Binding}". The command is actually on the DataContext of your Menu, which is why RelativeSource is used here.
What this is saying is you're going to pass a command the thing that was just clicked. Effectively, you're selecting the button you click. Then, your command just needs to set a SelectedThing property in your ViewModel equal to whatever Thing is passed to it. Hopefully you have implemented a class that implements ICommand to create your delegate commands. If you don't, there are a lot of articles out there on how to do it. If you don't know how, put a comment on this post and I'll add the source code to do it.
Then we have the "IsChecked" bad boy. We're actually doing a binding long-hand there. This is the more complicated piece, but it allows a DataTemplated item to actually bind to itself within a converter.
First, you need the proxy object, which is explained here:
http://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
Very simple to implement. Once it is done, the BindingProxy resource within your grid will work, and can act as an anchor back to the item bound to by the DataTemplate. The linked article explains why.
Then, you need a converter that compares two objects to each other.
public class ComparisonConverter : DependencyObject, IValueConverter
{
public object CompareTo
{
get { return (object)GetValue(CompareToProperty); }
set { SetValue(CompareToProperty, value); }
}
public static readonly DependencyProperty CompareToProperty =
DependencyProperty.Register("CompareTo", typeof(object), typeof(ComparisonConverter), new UIPropertyMetadata(null));
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (CompareTo != null)
{
return CompareTo.Equals(value);
}
else
{
return false;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
So now that binding will take the selected item from the DataContext of the menu, and compare it to whatever the ToggleButton is bound to. If the two objects match, the button appears clicked/selected. If they don't match, the button doesn't look selected.
So I do happen to have that BindingProxy and my converter in the same namespace. You don't necessarily have to do that. I just usually have a namespace for "Xaml Trick" classes that I have to program.
This is a lot to digest, and I'm happy to clarify anything.
One other thing...if you don't like the "ToggleButton" look, you can always style them to look completely different. The thing that having a ToggleButton buys you is the "IsChecked" property and the Command property. You can make the ContentTemplate look like anything you want, which gives you a lot of freedom in styling your menu.
If the ListBox has its ItemsSource set to a generic list of a complex entity, using ListBox.SelectedValue will get you the currently selected data.
For example:
public partial class NameListView : Window
{
/// <summary>
/// Constructor
/// </summary>
public NameListView()
{
List<string> names = new List<string>();
names.Add("John Doe");
names.Add("Jane Doe");
lbNameList.ItemsSource = names;
}
/// <summary>
/// Selection changed event handler for ListBox lbNameList
/// </summary>
void lbNameList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
{
string currentValue = lbNameList.SelectedValue.ToString();
MessageBox.Show("Currently selected value: " + currentValue);
}
}
}
If you have a property in your contextfile ( like Codebehind file or ViewModel ) that represents the currentSelectedItem then you can write the following in your xaml :
<ListView x:Name="MyList"
ItemsSource="MySource"
SelectedItem="{Binding Path=MyCurrentSelectedItem}" IsSynchronizedWithCurrentItem="True">
Codebehind / ViewModel
public MyType MyCurrentSelectedItem { get; set; }
I'm pretty new to ObservableCollections, but have built some code which I'm sure should work. Unfortunately it doesn't. The only thing that is not happening, is my GUI is not being updated. I know the values are being updated in the back (Checked using Debugger).
What am I doing wrong?
Here with a sample of my XAML for the Textblock:
<TextBlock Name="tbCallsOpen" Text="{Binding IndicatorValue}" />
Herewith sample of my code behind:
public partial class CurrentCalls : UserControl
{
Microsoft.SharePoint.Client.ListItemCollection spListItems;
ObservableCollection<CurrentCallIndicator> CallIndicators = new ObservableCollection<CurrentCallIndicator>();
public CurrentCalls()
{
InitializeComponent();
DispatcherTimer dispatchTimer = new DispatcherTimer();
dispatchTimer.Interval = new TimeSpan(0, 0, 20);
dispatchTimer.Tick += new EventHandler(BindData);
dispatchTimer.Start();
}
private void BindData(object sender, EventArgs args)
{
//splistitems is a sharepoint list. Data is being retrieved succesfully, no issues here.
foreach (var item in spListItems)
{
//My custom class which implements INotifyPropertyChanged
CurrentCallIndicator indicator = new CurrentCallIndicator();
indicator.IndicatorValue = item["MyValueColumn"];
//Adding to ObservableCollection
CallIndicators.Add(indicator);
}
//Setting Datacontext of a normal TextBlock
tbCallsOpen.DataContext = CallIndicators.First(z => z.IndicatorName == "somevalue");
}
}
You are most likely assuming that changes to the underlying items in the collection will raise the CollectionChanged event; however that is not how the ObservableCollection<T> works.
If you wanted this behavior you would need to roll your own implmentation and when a PropertyChanged event is fired within an item within your collection, you would then need to fire the CollectionChanged event.
Your code looks more-or-less correct to me, at first blush - though I wouldn't expect that you'd need to use an ObservableCollection<> to get the results you seem to be expecting: a simple List<> would work just fine.
If the debugger tells you that the DataContext is being updated correctly to the expected item, then the most likely issue is that there's a problem with how your binding is defined. If you're not seeing any binding errors reported in your debug window, then I'd look into Bea Stollnitz' article on debugging bindings. Most specifically, I often use the technique she suggests of a "DebugValueConverter", e.g.:
/// <summary>
/// Helps to debug bindings. Use like this: Content="{Binding PropertyName, Converter={StaticResource debugConverter}}"
/// </summary>
public class DebugConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
And then set a breakpoint in your converter, and watch what's happening. It's a hack and a kludge, but until we're all on SL5 (which has binding debugging built in), it's your best bet.
Ok, Sorted. I fixed the issue myself. Because I was updating the values in a loop, the ObservableCollection wasn't being updated properly. All I did in the beginning of the databinding method, was to Clear the collection : CallIndicators.Clear();
When I try to bind a valueconverter from a defined enum Status to brush, I get an error in my XAML designer:
'OKStatus' resource not found.
The application works fine runtime, but I'm not able to see my GUI in the designer.
My resources are defined in the color.xaml file, which is read at run time.
All code is within the same namespace
My XAML:
xmlns:config="clr-namespace:App.MyNamespace"
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="c:\Skins\Colors.xaml" />
<ResourceDictionary Source="c:\Skins\Common.xaml" />
</ResourceDictionary.MergedDictionaries>
<config:StatusConverter x:Key="StateConverter" />
<config:BoolConverter x:Key="BoolConverter" />
<config:BooleanConverter x:Key="BooleanConverter" />
</ResourceDictionary>
</UserControl.Resources>
and
Status
My converter:
[ValueConversion(typeof(bool), typeof(Brush))]
public class BoolConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
bool state = (bool)value;
FrameworkElement FrameElem = new FrameworkElement();
if (state == true)
return (FrameElem.FindResource("OKStatus") as Brush);
else
return (FrameElem.FindResource("ErrorStatus") as Brush);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return null;
}
}
In this code the frameElem wont have any knowledge of the resources I have defined I guess, so I need a way to get access to my resources during design.
Is this possible?
Yes, it is possible, and your guess is correct. Resource finding starts with the logical tree, and creating a new FrameworkElement() doesn't satisfy this. It's completely disconnected.
What you can do (and what you may have to do if N8's suggestion doesn't work), is to hand your converter a reference to the UserControl as the FrameworkElement to call FindResource() on.
The reason N8's suggestion probably won't work is that Application.Current.FindResource() probably starts at application-level resources and then goes to system resources, but the resources you're after are in the UserControl's resources. If they were placed in App.xaml's resources, it would work. However, I think Application.Current may be null at design-time.
The easiest way I can think of to do this is in your UserControl's constructor:
public MyUserControl(){
var boolconv = new BoolConverter();
boolconv.FrameworkElement = this;
this.Resources.Add( "BoolConverter", boolconv );
InitializeComponent();
}
I'm pretty sure it goes before InitializeComponent(), rather than after.
Doing this in XAML would be more complicated, as you probably have to add a DependencyProperty to your converter so that you could bind the UserControl to it. I think that would be going overboard.
Another way is to put TrueBrush and FalseBrush properties on your converter and assign them in XAML, which is what I tend to do so that my converters are vague and general-use. (NB: Names are slightly different.)
<config:BoolToBrushConverter x:Key="Bool2Brush"
TrueBrush="{StaticResource OKStatusBrush}"
FalseBrush="{StaticResource ErrorStatusBrush}" />
I think the issue is that you are trying to find the resource out of a framework element not in the visual tree. Could you try the following instead?
Application.Current.FindResource("OKStatus") as Brush;
As I have learned by TechNet Wiki, there is necessary to use MultiValue Converter and MultiValueBinding to get correct registred converter and correct FrameworkElement by the UserControl.
XAML Example:
<TextBlock x:Name="tb1" Margin="20">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}"/>
<Binding Path="MyValue"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Then the converter declaration can looks :
public class MyConverter : IMultiValueConverter
{
FrameworkElement myControl;
object theValue;
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
myControl = values[0] as FrameworkElement;
theValue = values[1];
return myControl.FindResource(">>resource u need<<");
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
.....
}
}
The detail explanation is:
https://social.technet.microsoft.com/wiki/contents/articles/12423.wpfhowto-pass-and-use-a-control-in-it-s-own-valueconverter-for-convertconvertback.aspx
I have run into this problem as well. I think that calling Application.Current is the best way to get at resources from an IValueConverter, since they are not defined on a per window, or page or control, level. This would require the resources to be at least application-level, as stated above.
However, since the Application.Current reference is set to null in the designer, this method will always break the designer. What you appear to have done is given something for the designer to display, while you have given your running application access to resources in the converter.
For all of you out there with this issue, you don't need to implement the Kludge that lewi implemented; this is only if you want the designer surface to load. It does not affect your application while running as the Application.Current call has something to do.
Actually what I ended up doing (for now) was to change from FindResource to TryFindResource, and put the statements in a try/catch block.
This seems to work so far.
try
{
if (state == true)
return (FrameElem.TryFindResource("OKStatus") as Brush);
else
return (FrameElem.TryFindResource("ErrorStatus") as Brush);
}
catch (ResourceReferenceKeyNotFoundException)
{
return new SolidColorBrush(Colors.LightGray);
}
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. :)