I recently implemented a solution into my code that allows me to bind to my command in my view model. Here is a link to the method that I used: https://code.msdn.microsoft.com/Event-to-Command-24d903c8. I used the 2nd method in the link. You can assume for all intents and purposes that my code is very similar to this code. This works just fine. However, I need to bind a command parameter for this double click as well. How would I set that up?
Here is some background on my project. Some of the setup behind this project may seem odd, but it must be done in this way, due to a bunch of details that I won't get into here. The first thing to note would be that this binding setup is happening inside of a multivalue converter. Here is my code generating the new element:
DataTemplate dt = new DataTemplate();
dt.DataType = typeof(Button);
FrameworkElementFactory btn = new FrameworkElementFactory(typeof(Button));
btn.SetValue(Attached.DoubleClickCommandProperty, ((CardManagementViewModel)values[1]).ChangeImageCommand);
dt.VisualTree = btn;
values[1] is the DataContext, which is the viewmodel here. The View Model contains the following:
private RelayCommand _ChangeImageCommand;
public ICommand ChangeImageCommand
{
get
{
if (_ChangeImageCommand == null)
{
_ChangeImageCommand = new RelayCommand(
param => this.ChangeImage(param)
);
}
return _ChangeImageCommand;
}
}
private void ChangeImage(object cardParam)
{
}
How can I pass that command parameter? I have bound all this stuff using XAML many times before, but have never had to do it from C#. Thank you for any and all help!
EDIT
Here is a complete sample of my issue. Though I know that this sample has no practical purpose to do things the way it does, for the sake of this problem, we will just run with it.
Let's say that I have an ObservableCollection of strings that I want to show. These are contained in viewmodel.
private ObservableCollection<string> _MyList;
public ObservableCollection<string> MyList { get { return _MyList; } set { if (_MyList != value) { _MyList = value; RaisePropertyChanged("MyList"); } } }
public ViewModel()
{
MyList = new ObservableCollection<string>();
MyList.Add("str1");
MyList.Add("str2");
MyList.Add("str3");
}
So the guy in charge of the UI on my team hands me this
<ContentControl>
<ContentControl.Content>
<MultiBinding Converter="{StaticResource ResourceKey=MyConverter}">
<Binding Path="MyList"/>
<Binding />
</MultiBinding>
</ContentControl.Content>
</ContentControl>
Now lets say that the UI person and my project manager decide to conspire against me to make my life a living hell, so they tell me that I need to create a listbox to display these items as buttons, not in the XAML, but in the converter that the ContentControl's Content is bound to. So I do this:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
ListBox lb = new ListBox();
lb.ItemsSource = (ObservableCollection<string>)values[0];
DataTemplate dt = new DataTemplate();
dt.DataType = typeof(Button);
FrameworkElementFactory btn = new FrameworkElementFactory(typeof(Button));
btn.SetValue(Button.WidthProperty, 100D);
btn.SetValue(Button.HeightProperty, 50D);
btn.SetBinding(Button.ContentProperty, new Binding());
dt.VisualTree = btn;
lb.ItemTemplate = dt;
return lb;
}
This successful displays the listbox, with all the items as buttons. The next day, my idiot project manager creates a new command in the view model. It's purpose is to add the selected item in the listbox if one of the button's is double clicked. NOT SINGLE CLICKED, BUT DOUBLE CLICKED! This means that I can't use the CommandProperty or, more importantly, the CommandParameterProperty. His Command in the viewmodel looks something like this:
private RelayCommand _MyCommand;
public ICommand MyCommand
{
get
{
if (_MyCommand == null)
{
_MyCommand = new RelayCommand(
param => this.MyMethod(param)
);
}
return _MyCommand;
}
}
private void MyMethod(object myParam)
{
MyList.Add(myParam.ToString());
}
So after some googling, I find a class that turns my DoubleClick event into an attached property. Here is that class:
public class Attached
{
static ICommand command;
public static ICommand GetDoubleClickCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(DoubleClickCommandProperty);
}
public static void SetDoubleClickCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(DoubleClickCommandProperty, value);
}
// Using a DependencyProperty as the backing store for DoubleClickCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DoubleClickCommandProperty =
DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(Attached), new UIPropertyMetadata(null, CommandChanged));
static void CommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var fe = obj as FrameworkElement;
command = e.NewValue as ICommand;
fe.AddHandler(Button.MouseDoubleClickEvent, new RoutedEventHandler(ExecuteCommand));
}
static void ExecuteCommand(object sender, RoutedEventArgs e)
{
var ele = sender as Button;
command.Execute(null);
}
}
Back in the converter then, I put this line right above dt.VisualTree = btn;:
btn.SetValue(Attached.DoubleClickCommandProperty, ((ViewModel)values[1]).MyCommand);
This successfully hits my project manager's command, but I still need to pass the listbox's selected item. My Project Manager then tells me that I am not allowed to touch the viewmodel anymore. This is where I am stuck. How can I still send the listbox's selected item to my project manager's command in the view model?
Here is the full code files for this example:
ViewModel.cs
using System.Collections.ObjectModel;
using System.Windows.Input;
using WpfApplication2.Helpers;
namespace WpfApplication2
{
public class ViewModel : ObservableObject
{
private ObservableCollection<string> _MyList;
private RelayCommand _MyCommand;
public ObservableCollection<string> MyList { get { return _MyList; } set { if (_MyList != value) { _MyList = value; RaisePropertyChanged("MyList"); } } }
public ViewModel()
{
MyList = new ObservableCollection<string>();
MyList.Add("str1");
MyList.Add("str2");
MyList.Add("str3");
}
public ICommand MyCommand
{
get
{
if (_MyCommand == null)
{
_MyCommand = new RelayCommand(
param => this.MyMethod(param)
);
}
return _MyCommand;
}
}
private void MyMethod(object myParam)
{
MyList.Add(myParam.ToString());
}
}
}
MainWindow.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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:helpers="clr-namespace:WpfApplication2.Helpers"
xmlns:Converters="clr-namespace:WpfApplication2.Helpers.Converters"
xmlns:local="clr-namespace:WpfApplication2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Window.Resources>
<Converters:MyConverter x:Key="MyConverter"/>
</Window.Resources>
<ContentControl>
<ContentControl.Content>
<MultiBinding Converter="{StaticResource ResourceKey=MyConverter}">
<Binding Path="MyList"/>
<Binding />
</MultiBinding>
</ContentControl.Content>
</ContentControl>
</Window>
MyConverter.cs
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApplication2.Helpers.Converters
{
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
ListBox lb = new ListBox();
lb.ItemsSource = (ObservableCollection<string>)values[0];
DataTemplate dt = new DataTemplate();
dt.DataType = typeof(Button);
FrameworkElementFactory btn = new FrameworkElementFactory(typeof(Button));
btn.SetValue(Button.WidthProperty, 100D);
btn.SetValue(Button.HeightProperty, 50D);
btn.SetBinding(Button.ContentProperty, new Binding());
btn.SetValue(Attached.DoubleClickCommandProperty, ((ViewModel)values[1]).MyCommand);
// Somehow create binding so that I can pass the selected item of the listbox to the
// above command when the button is double clicked.
dt.VisualTree = btn;
lb.ItemTemplate = dt;
return lb;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Attached.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication2.Helpers
{
public class Attached
{
static ICommand command;
public static ICommand GetDoubleClickCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(DoubleClickCommandProperty);
}
public static void SetDoubleClickCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(DoubleClickCommandProperty, value);
}
// Using a DependencyProperty as the backing store for DoubleClickCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DoubleClickCommandProperty =
DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(Attached), new UIPropertyMetadata(null, CommandChanged));
static void CommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var fe = obj as FrameworkElement;
command = e.NewValue as ICommand;
fe.AddHandler(Button.MouseDoubleClickEvent, new RoutedEventHandler(ExecuteCommand));
}
static void ExecuteCommand(object sender, RoutedEventArgs e)
{
var ele = sender as Button;
command.Execute(null);
}
}
}
ObservableObject.cs
using System;
using System.ComponentModel;
using System.Diagnostics;
namespace WpfApplication2.Helpers
{
public class ObservableObject : INotifyPropertyChanged
{
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public virtual void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
#region INotifyPropertyChanged Members
/// <summary>
/// Raises the PropertyChange event for the property specified
/// </summary>
/// <param name="propertyName">Property name to update. Is case-sensitive.</param>
public virtual void RaisePropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
OnPropertyChanged(propertyName);
}
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
}
}
RelayCommand.cs
using System;
using System.Diagnostics;
using System.Windows.Input;
namespace WpfApplication2.Helpers
{
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion // ICommand Members
}
}
Again, thank you for any help!!!
The code you posted isn't actually a minimal or complete example. At the very least, it's missing the CardManagementViewModel type, and of course the example appears to be based on the original code, with no attempt to reduce it to a minimal example.
As such, I did not spend much time looking through all of the code, never mind did I bother to compile and run it. However, the main thing that was missing in your original edit is the implementation of the attached property. So with that in hand, I propose you change your Attached class so it looks like this:
public class Attached
{
public static ICommand GetDoubleClickCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(DoubleClickCommandProperty);
}
public static void SetDoubleClickCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(DoubleClickCommandProperty, value);
}
public static object GetDoubleClickCommandParameter(DependencyObject obj)
{
return obj.GetValue(DoubleClickCommandParameterProperty);
}
public static void SetDoubleClickCommandParameter(DependencyObject obj, object value)
{
obj.SetValue(DoubleClickCommandParameterProperty, value);
}
// Using a DependencyProperty as the backing store for DoubleClickCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DoubleClickCommandProperty =
DependencyProperty.RegisterAttached("DoubleClickCommand", typeof(ICommand), typeof(Attached), new UIPropertyMetadata(null, CommandChanged));
public static readonly DependencyProperty DoubleClickCommandParameterProperty =
DependencyProperty.RegisterAttached("DoubleClickCommandParameter", typeof(object), typeof(Attached));
static void CommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var fe = obj as FrameworkElement;
if (e.OldValue == null && e.NewValue != null)
{
fe.AddHandler(Button.MouseDoubleClickEvent, ExecuteCommand);
}
else if (e.OldValue != null && e.NewValue == null)
{
fe.RemoveHandler(Button.MouseDoubleClickEvent, ExecuteCommand);
}
}
static void ExecuteCommand(object sender, RoutedEventArgs e)
{
var ele = sender as Button;
ICommand command = GetDoubleClickCommand(ele);
object parameter = GetDoubleClickCommandParameter(ele);
command.Execute(parameter);
}
}
Caveat: the above is just typed in the web browser. Because of the lack of a good Minimal, Complete, and Verifiable code example, I did not bother to try to compile, never mind run, the above. I trust that if there are typographical or logic errors, they are minimal and you'll be able to easily understand what the code actually ought to be, based on your goals.
The main thing here is that I've added the Attached.DoubleClickCommandParameter attached property. This will allow you to set the command parameter at the same time as the command itself.
I also changed a couple of other implementation details:
The command and its parameter are retrieved for the given object when the event is raised, instead of saving the ICommand in a static field as your implementation had it. The way your code had it, you could only ever have one command at a time. If you tried to set the attached property on multiple elements, and used more than one ICommand value, you still would only ever get the most recently-set ICommand. With my change, you'll always get the command you set.
I changed the code dealing with changes in the property, so that it only ever adds the handler if the previous value was null and the new value is non-null, and I also changed the code to remove the handler if and when the value is ever changed from a non-null value back to null.
Then you can use the attached properties in code-behind like this:
Attached.SetDoubleClickCommand(btn, ((CardManagementViewModel)values[1]).ChangeImageCommand);
Attached.SetDoubleClickCommandParameter(btn, ((CardManagementViewModel)values[1]).ChangeImageCommandParameter);
Note that I'm assuming you have a ChangeImageCommandParameter property that stores the parameter you want to send. You can of course set the property value to whatever you want, such as e.g. a value referring to the selected item or something else.
I also changed the setting to call the Attached class's property setter methods, which is a more proper use of the attached property abstraction in WPF. Granted, in most implementations it's exactly the same as calling the SetValue() method directly, but it is better to go through the attached property's methods, in case they have customized the behavior in some way.
Now, all that said, I will reiterate that your broader design is very wrong in several different ways. By ignoring the conventional approach of MVVM or similar, tying UI configuration and behaviors to the view models, and especially of using the converter as a place to actually modify the state of the objects, you are creating a system that is likely to have a number of subtle, difficult-to-find, and nearly-impossible-to-fix bugs in it.
But that's mostly independent of the question of how to use the attached property. Even in a well-designed WPF program, attached properties have their place, and I hope that the above gives you a better idea of how you would extend your existing attached property so that it supports additional values (e.g. the CommandProperty value).
Related
My English skill is poor because I'm not a native English speaker.
I have created as following a behavior that working at the TextBox control.
The behavior has a collection-type DP named Items.
class HighlightBehavior : Behavior<TextBox>
{
public List<TextStyle>Items
{
get { return (List<TextStyle>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(List<TextStyle>), typeof(HighlightBehavior), new PropertyMetadata(ItemsChanged));
private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// break point
}
}
And... I have created a MainWindow to use as following code above behavior.
<MainWindow>
<TextBox>
<i:interaction.Behaviors>
<behavior:HighlightBehavior/>
</i:interaction.Behavior>
</TextBox>
</MainWindow>
And I have written a MainWindowViewModel that has a collection-type DP named HighlightItems.
class MainWindowViewModel : ViewModelBase
{
public List<TextStyle> HighlightItems
{
get { return (List<TextStyle>)GetValue(HighlightItemsProperty ); }
set { SetValue(HighlightItemsProperty , value); }
}
public static readonly DependencyProperty HighlightItemsProperty =
DependencyProperty.Register("HighlightItems", typeof(List<TextStyle>), typeof(HighlightBehavior), new PropertyMetadata(null));
public MainWindowViewModel()
{
SetValue(HighlightItemsProperty, new List<TextStyle>());
}
}
And I have bound the MainWindowViewModel to MainWindow and connected HighlightItems(DP) of MainWindowViewModel with Items(DP) of HighlightBehavior as the following code.
<MainWindow>
<TextBox>
<i:interaction.Behaviors>
<behavior:HighlightBehavior Items="{Binding HighlightItems, Mode=TwoWay}"/>
</i:interaction.Behavior>
</TextBox>
</MainWindow>
To sum up, the structure is the following figure.
I have expected that ItemsChanged of HighlightBehavior is called whenever Items changed.
But it is not called.
I want to get notification whenever collection-type DP(Items) of HighlightBehavior is changed.
What must I do to reach this goal?
Thank you for reading.
I'll wait for an answer.
I believe what you're looking for is ObservableCollection. This is a special type of collection which raises its CollectionChanged event whenever an item is added, removed, changed or moved.
I recommend the following:
Instead of declaring HighlightItems as List<TextStyle>, declare it as ObservableCollection<TextStyle>.
Add another method to HighlightBehavior to handle CollectionChanged, for example:
HighlightItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
Your current implementation of ItemsChanged will be called whenever HighlightItems is set. Use that to attach an event handler to CollectionChanged like so:
var col = (ObservableCollection<TextStyle>)e.NewValue;
if (col != null) { col.CollectionChanged += HighlightItemsCollectionChanged; }
Don't forget to remove any existing event handler to the previous collection in case HighlightItems is set move than once. You can add this to ItemsChanged along with the previous snippet:
col = (ObservableCollection<TextStyle>)e.OldValue;
if (col != null) { col.CollectionChanged -= HighlightItemsCollectionChanged; }
HighlightItemsCollectionChanged will now be called whenever an item is added or removed from HighlightItems. Do whatever you need to do in this method, or if you want the code to also run when the collection itself is replaced, you can make another method that actually does what you want, and then call that method from both ItemsChanged and HighlightItemsCollectionChanged.
Thank you.
I have changed the code following your advice and now I can receive a notification when the element of the collection is changed.
I knew about the ObservableCollection but I didn't know how to use right about CollectionChanged event.
In fact, previous I tried to use the ObservableCollection and registered the CollectionChanged delegate method at the Constructer as following but it is not called.
public ObservableCollection<TextStyle> Items
{
get { return (ObservableCollection<TextStyle>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
// Using a DependencyProperty as the backing store for Items. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<TextStyle>), typeof(HighlightBehavior),
new PropertyMetaData(null));
public HighlightBehavior()
{
SetValue(ItemsProperty, new ObservableCollection<TextStyle>());
Items.CollectionChanged += OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// The code when the collection is changed.
}
Now, I have registered the CollectionChanged delegate method in the PropertyChangedCallback method as following and it(OnCollectionChanged method at the following code) is called.
public ObservableCollection<TextStyle> Items
{
get { return (ObservableCollection<TextStyle>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
// Using a DependencyProperty as the backing store for Items. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<TextStyle>), typeof(HighlightBehavior),
new PropertyMetaData(ItemsChanged));
private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var col = (ObservableCollection<TextStyle>)e.NewValue;
if (col != null) { col.CollectionChanged += OnCollectionChanged; ; }
col = (ObservableCollection<TextStyle>)e.OldValue;
if (col != null) { col.CollectionChanged -= OnCollectionChanged; }
}
public HighlightBehavior()
{
SetValue(ItemsProperty, new ObservableCollection<TextStyle>());
}
Thank you for your answer in detail.
I have a dropdown (ComboBox) that displays all the com ports available on a machine. Now, ports come and go when you connect and disconnect devices.
For performance reasons I don't want to keep calling System.IO.Ports.SerialPort.GetPortNames(), but rather just call that when the user clicks on the Combobox? Is this possible? Is there an MVVM approach to this problem?
Use InvokeCommandAction.
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
DropDownOpenedCommand is an ICommand property on your ViewModel.
<ComboBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="DropDownOpened">
<i:InvokeCommandAction Command="{Binding DropDownOpenedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Edit: obviously DropDownOpened not SelectionChanged, as Patrice commented.
You can use something like MVVMLight's EventToCommand to accomplish this. Basically, the event of clicking the combo would be hooked to your MVVM command binding, which would then fire the method that calls GetPortNames().
Here are some alternatives:
MVVM Light: Adding EventToCommand in XAML without Blend, easier way or snippet? (check the accepted answer)
http://www.danharman.net/2011/08/05/binding-wpf-events-to-mvvm-viewmodel-commands/ (Prism)
What I would recommend is scrapping the 'only update on clicks' idea, and just use binding and notifications for this (unless for some reason you think there will be so many Connect/Disconnect events it will slow your system). The simplest version of that would be a dependency property.
Provide an IObservableList<Port> property as a dependency property on your ViewModel like this:
/// <summary>
/// Gets or sets...
/// </summary>
public IObservableList<Port> Ports
{
get { return (IObservableList<Port>)GetValue(PortsProperty); }
set { SetValue(PortsProperty, value); }
}
public static readonly DependencyProperty PortsProperty = DependencyProperty.Register("Ports", typeof(IObservableList<Port>), typeof(MyViewModelClass), new PropertyMetadata(new ObservableList<Port>));
Now you may add/remove items to/from that list whenever you connect or disconnect devices, just do not replace the list. This will force the list to send off a ListChangedEvent for each action on the list, and the ComboBox (or any other bound UI) will react to those events.
This should be performant enough for you, as this will only cause the UI ComboBox to update whenever an event goes through.
I took a stab at routing events to a command:
XAML:
<ComboBox
ItemsSource="{Binding Items}"
local:ControlBehavior.Event="SelectionChanged"
local:ControlBehavior.Command="{Binding Update}" />
Code:
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
namespace StackOverflow
{
public class ControlBehavior
{
public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(ControlBehavior));
public static DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ControlBehavior));
public static DependencyProperty EventProperty = DependencyProperty.RegisterAttached("Event", typeof(string), typeof(ControlBehavior), new PropertyMetadata(PropertyChangedCallback));
public static void EventHandler(object sender, EventArgs e)
{
var s = (sender as DependencyObject);
if (s != null)
{
var c = (ICommand)s.GetValue(CommandProperty);
var p = s.GetValue(CommandParameterProperty);
if (c != null && c.CanExecute(s))
c.Execute(s);
}
}
public static void PropertyChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs a)
{
if (a.Property == EventProperty)
{
EventInfo ev = o.GetType().GetEvent((string)a.NewValue);
if (ev != null)
{
var del = Delegate.CreateDelegate(ev.EventHandlerType, typeof(ControlBehavior).GetMethod("EventHandler"));
ev.AddEventHandler(o, del);
}
}
}
public string GetEvent(UIElement element)
{
return (string)element.GetValue(EventProperty);
}
public static void SetEvent(UIElement element, string value)
{
element.SetValue(EventProperty, value);
}
public ICommand GetCommand(UIElement element)
{
return (ICommand)element.GetValue(CommandProperty);
}
public static void SetCommand(UIElement element, ICommand value)
{
element.SetValue(CommandProperty, value);
}
public object GetCommandParameter(UIElement element)
{
return element.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(UIElement element, object value)
{
element.SetValue(CommandParameterProperty, value);
}
}
}
I want to include an AvalonEdit TextEditor control into my MVVM application. The first thing I require is to be able to bind to the TextEditor.Text property so that I can display text. To do this I have followed and example that was given in Making AvalonEdit MVVM compatible. Now, I have implemented the following class using the accepted answer as a template
public sealed class MvvmTextEditor : TextEditor, INotifyPropertyChanged
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MvvmTextEditor),
new PropertyMetadata((obj, args) =>
{
MvvmTextEditor target = (MvvmTextEditor)obj;
target.Text = (string)args.NewValue;
})
);
public new string Text
{
get { return base.Text; }
set { base.Text = value; }
}
protected override void OnTextChanged(EventArgs e)
{
RaisePropertyChanged("Text");
base.OnTextChanged(e);
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string info)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
Where the XAML is
<Controls:MvvmTextEditor HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
FontFamily="Consolas"
FontSize="9pt"
Margin="2,2"
Text="{Binding Text, NotifyOnSourceUpdated=True, Mode=TwoWay}"/>
Firstly, this does not work. The Binding is not shown in Snoop at all (not red, not anything, in fact I cannot even see the Text dependency property).
I have seen this question which is exactly the same as mine Two-way binding in AvalonEdit doesn't work but the accepted answer does not work (at least for me). So my question is:
How can I perform two way binding using the above method and what is the correct implementation of my MvvmTextEditor class?
Thanks for your time.
Note: I have my Text property in my ViewModel and it implements the required INotifyPropertyChanged interface.
Create a Behavior class that will attach the TextChanged event and will hook up the dependency property that is bound to the ViewModel.
AvalonTextBehavior.cs
public sealed class AvalonEditBehaviour : Behavior<TextEditor>
{
public static readonly DependencyProperty GiveMeTheTextProperty =
DependencyProperty.Register("GiveMeTheText", typeof(string), typeof(AvalonEditBehaviour),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, PropertyChangedCallback));
public string GiveMeTheText
{
get { return (string)GetValue(GiveMeTheTextProperty); }
set { SetValue(GiveMeTheTextProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
if (AssociatedObject != null)
AssociatedObject.TextChanged += AssociatedObjectOnTextChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
AssociatedObject.TextChanged -= AssociatedObjectOnTextChanged;
}
private void AssociatedObjectOnTextChanged(object sender, EventArgs eventArgs)
{
var textEditor = sender as TextEditor;
if (textEditor != null)
{
if (textEditor.Document != null)
GiveMeTheText = textEditor.Document.Text;
}
}
private static void PropertyChangedCallback(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var behavior = dependencyObject as AvalonEditBehaviour;
if (behavior.AssociatedObject!= null)
{
var editor = behavior.AssociatedObject as TextEditor;
if (editor.Document != null)
{
var caretOffset = editor.CaretOffset;
editor.Document.Text = dependencyPropertyChangedEventArgs.NewValue.ToString();
editor.CaretOffset = caretOffset;
}
}
}
}
View.xaml
<avalonedit:TextEditor
WordWrap="True"
ShowLineNumbers="True"
LineNumbersForeground="Magenta"
x:Name="textEditor"
FontFamily="Consolas"
SyntaxHighlighting="XML"
FontSize="10pt">
<i:Interaction.Behaviors>
<controls:AvalonEditBehaviour GiveMeTheText="{Binding Test, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</i:Interaction.Behaviors>
</avalonedit:TextEditor>
i must be defined as
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
ViewModel.cs
private string _test;
public string Test
{
get { return _test; }
set { _test = value; }
}
That should give you the Text and push it back to the ViewModel.
Create a BindableAvalonEditor class with a two-way binding on the Text property.
I was able to establish a two-way binding with the latest version of AvalonEdit by combining Jonathan Perry's answer and 123 456 789 0's answer. This allows a direct two-way binding without the need for behaviors.
Here is the source code...
public class BindableAvalonEditor : ICSharpCode.AvalonEdit.TextEditor, INotifyPropertyChanged
{
/// <summary>
/// A bindable Text property
/// </summary>
public new string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
RaisePropertyChanged("Text");
}
}
/// <summary>
/// The bindable text property dependency property
/// </summary>
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(BindableAvalonEditor),
new FrameworkPropertyMetadata
{
DefaultValue = default(string),
BindsTwoWayByDefault = true,
PropertyChangedCallback = OnDependencyPropertyChanged
}
);
protected static void OnDependencyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var target = (BindableAvalonEditor)obj;
if (target.Document != null)
{
var caretOffset = target.CaretOffset;
var newValue = args.NewValue;
if (newValue == null)
{
newValue = "";
}
target.Document.Text = (string)newValue;
target.CaretOffset = Math.Min(caretOffset, newValue.ToString().Length);
}
}
protected override void OnTextChanged(EventArgs e)
{
if (this.Document != null)
{
Text = this.Document.Text;
}
base.OnTextChanged(e);
}
/// <summary>
/// Raises a property changed event
/// </summary>
/// <param name="property">The name of the property that updates</param>
public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I like none of these solutions. The reason the author didn't create a dependency property on Text is for performance reason. Working around it by creating an attached property means the text string must be recreated on every key stroke. On a 100mb file, this can be a serious performance issue. Internally, it only uses a document buffer and will never create the full string unless requested.
It exposes another property, Document, which is a dependency property, and it exposes the Text property to construct the string only when needed. Although you can bind to it, it would mean designing your ViewModel around a UI element which defeats the purpose of having a ViewModel UI-agnostic. I don't like that option either.
Honestly, the cleanest(ish) solution is to create 2 events in your ViewModel, one to display the text and one to update the text. Then you write a one-line event handler in your code-behind, which is fine since it's purely UI-related. That way, you construct and assign the full document string only when it's truly needed. Additionally, you don't even need to store (nor update) the text in the ViewModel. Just raise DisplayScript and UpdateScript when it is needed.
It's not an ideal solution, but there are less drawbacks than any other method I've seen.
TextBox also faces a similar issue, and it solves it by internally using a DeferredReference object that constructs the string only when it is really needed. That class is internal and not available to the public, and the Binding code is hard-coded to handle DeferredReference in a special way. Unfortunately there doesn't seen to be any way of solving the problem in the same way as TextBox -- perhaps unless TextEditor would inherit from TextBox.
Another nice OOP approach is to download the source code of AvalonEdit (it's open sourced), and creating a new class that inherits from TextEditor class (the main editor of AvalonEdit).
What you want to do is basically override the Text property and implement an INotifyPropertyChanged version of it, using dependency property for the Text property and raising the OnPropertyChanged event when text is changed (this can be done by overriding the OnTextChanged() method.
Here's a quick code (fully working) example that works for me:
public class BindableTextEditor : TextEditor, INotifyPropertyChanged
{
/// <summary>
/// A bindable Text property
/// </summary>
public new string Text
{
get { return base.Text; }
set { base.Text = value; }
}
/// <summary>
/// The bindable text property dependency property
/// </summary>
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(BindableTextEditor), new PropertyMetadata((obj, args) =>
{
var target = (BindableTextEditor)obj;
target.Text = (string)args.NewValue;
}));
protected override void OnTextChanged(EventArgs e)
{
RaisePropertyChanged("Text");
base.OnTextChanged(e);
}
/// <summary>
/// Raises a property changed event
/// </summary>
/// <param name="property">The name of the property that updates</param>
public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
For those wondering about an MVVM implementation using AvalonEdit, here is one of the ways it can be done, first we have the class
/// <summary>
/// Class that inherits from the AvalonEdit TextEditor control to
/// enable MVVM interaction.
/// </summary>
public class CodeEditor : TextEditor, INotifyPropertyChanged
{
// Vars.
private static bool canScroll = true;
/// <summary>
/// Default constructor to set up event handlers.
/// </summary>
public CodeEditor()
{
// Default options.
FontSize = 12;
FontFamily = new FontFamily("Consolas");
Options = new TextEditorOptions
{
IndentationSize = 3,
ConvertTabsToSpaces = true
};
}
#region Text.
/// <summary>
/// Dependancy property for the editor text property binding.
/// </summary>
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(CodeEditor),
new PropertyMetadata((obj, args) =>
{
CodeEditor target = (CodeEditor)obj;
target.Text = (string)args.NewValue;
}));
/// <summary>
/// Provide access to the Text.
/// </summary>
public new string Text
{
get { return base.Text; }
set { base.Text = value; }
}
/// <summary>
/// Return the current text length.
/// </summary>
public int Length
{
get { return base.Text.Length; }
}
/// <summary>
/// Override of OnTextChanged event.
/// </summary>
protected override void OnTextChanged(EventArgs e)
{
RaisePropertyChanged("Length");
base.OnTextChanged(e);
}
/// <summary>
/// Event handler to update properties based upon the selection changed event.
/// </summary>
void TextArea_SelectionChanged(object sender, EventArgs e)
{
this.SelectionStart = SelectionStart;
this.SelectionLength = SelectionLength;
}
/// <summary>
/// Event that handles when the caret changes.
/// </summary>
void TextArea_CaretPositionChanged(object sender, EventArgs e)
{
try
{
canScroll = false;
this.TextLocation = TextLocation;
}
finally
{
canScroll = true;
}
}
#endregion // Text.
#region Caret Offset.
/// <summary>
/// DependencyProperty for the TextEditorCaretOffset binding.
/// </summary>
public static DependencyProperty CaretOffsetProperty =
DependencyProperty.Register("CaretOffset", typeof(int), typeof(CodeEditor),
new PropertyMetadata((obj, args) =>
{
CodeEditor target = (CodeEditor)obj;
if (target.CaretOffset != (int)args.NewValue)
target.CaretOffset = (int)args.NewValue;
}));
/// <summary>
/// Access to the SelectionStart property.
/// </summary>
public new int CaretOffset
{
get { return base.CaretOffset; }
set { SetValue(CaretOffsetProperty, value); }
}
#endregion // Caret Offset.
#region Selection.
/// <summary>
/// DependencyProperty for the TextLocation. Setting this value
/// will scroll the TextEditor to the desired TextLocation.
/// </summary>
public static readonly DependencyProperty TextLocationProperty =
DependencyProperty.Register("TextLocation", typeof(TextLocation), typeof(CodeEditor),
new PropertyMetadata((obj, args) =>
{
CodeEditor target = (CodeEditor)obj;
TextLocation loc = (TextLocation)args.NewValue;
if (canScroll)
target.ScrollTo(loc.Line, loc.Column);
}));
/// <summary>
/// Get or set the TextLocation. Setting will scroll to that location.
/// </summary>
public TextLocation TextLocation
{
get { return base.Document.GetLocation(SelectionStart); }
set { SetValue(TextLocationProperty, value); }
}
/// <summary>
/// DependencyProperty for the TextEditor SelectionLength property.
/// </summary>
public static readonly DependencyProperty SelectionLengthProperty =
DependencyProperty.Register("SelectionLength", typeof(int), typeof(CodeEditor),
new PropertyMetadata((obj, args) =>
{
CodeEditor target = (CodeEditor)obj;
if (target.SelectionLength != (int)args.NewValue)
{
target.SelectionLength = (int)args.NewValue;
target.Select(target.SelectionStart, (int)args.NewValue);
}
}));
/// <summary>
/// Access to the SelectionLength property.
/// </summary>
public new int SelectionLength
{
get { return base.SelectionLength; }
set { SetValue(SelectionLengthProperty, value); }
}
/// <summary>
/// DependencyProperty for the TextEditor SelectionStart property.
/// </summary>
public static readonly DependencyProperty SelectionStartProperty =
DependencyProperty.Register("SelectionStart", typeof(int), typeof(CodeEditor),
new PropertyMetadata((obj, args) =>
{
CodeEditor target = (CodeEditor)obj;
if (target.SelectionStart != (int)args.NewValue)
{
target.SelectionStart = (int)args.NewValue;
target.Select((int)args.NewValue, target.SelectionLength);
}
}));
/// <summary>
/// Access to the SelectionStart property.
/// </summary>
public new int SelectionStart
{
get { return base.SelectionStart; }
set { SetValue(SelectionStartProperty, value); }
}
#endregion // Selection.
#region Properties.
/// <summary>
/// The currently loaded file name. This is bound to the ViewModel
/// consuming the editor control.
/// </summary>
public string FilePath
{
get { return (string)GetValue(FilePathProperty); }
set { SetValue(FilePathProperty, value); }
}
// Using a DependencyProperty as the backing store for FilePath.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty FilePathProperty =
DependencyProperty.Register("FilePath", typeof(string), typeof(CodeEditor),
new PropertyMetadata(String.Empty, OnFilePathChanged));
#endregion // Properties.
#region Raise Property Changed.
/// <summary>
/// Implement the INotifyPropertyChanged event handler.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string caller = null)
{
var handler = PropertyChanged;
if (handler != null)
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
#endregion // Raise Property Changed.
}
Then in your view where you want to have AvalonEdit, you can do
...
<Grid>
<Local:CodeEditor
x:Name="CodeEditor"
FilePath="{Binding FilePath,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
WordWrap="{Binding WordWrap,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
ShowLineNumbers="{Binding ShowLineNumbers,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
SelectionLength="{Binding SelectionLength,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
SelectionStart="{Binding SelectionStart,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"
TextLocation="{Binding TextLocation,
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnTargetUpdated=True}"/>
</Grid>
Where this can be placed in a UserControl or Window or what ever, then in the ViewModel for this view we have (where I am using Caliburn Micro for the MVVM framework stuff)
public string FilePath
{
get { return filePath; }
set
{
if (filePath == value)
return;
filePath = value;
NotifyOfPropertyChange(() => FilePath);
}
}
/// <summary>
/// Should wrap?
/// </summary>
public bool WordWrap
{
get { return wordWrap; }
set
{
if (wordWrap == value)
return;
wordWrap = value;
NotifyOfPropertyChange(() => WordWrap);
}
}
/// <summary>
/// Display line numbers?
/// </summary>
public bool ShowLineNumbers
{
get { return showLineNumbers; }
set
{
if (showLineNumbers == value)
return;
showLineNumbers = value;
NotifyOfPropertyChange(() => ShowLineNumbers);
}
}
/// <summary>
/// Hold the start of the currently selected text.
/// </summary>
private int selectionStart = 0;
public int SelectionStart
{
get { return selectionStart; }
set
{
selectionStart = value;
NotifyOfPropertyChange(() => SelectionStart);
}
}
/// <summary>
/// Hold the selection length of the currently selected text.
/// </summary>
private int selectionLength = 0;
public int SelectionLength
{
get { return selectionLength; }
set
{
selectionLength = value;
UpdateStatusBar();
NotifyOfPropertyChange(() => SelectionLength);
}
}
/// <summary>
/// Gets or sets the TextLocation of the current editor control. If the
/// user is setting this value it will scroll the TextLocation into view.
/// </summary>
private TextLocation textLocation = new TextLocation(0, 0);
public TextLocation TextLocation
{
get { return textLocation; }
set
{
textLocation = value;
UpdateStatusBar();
NotifyOfPropertyChange(() => TextLocation);
}
}
And that's it! Done.
I hope this helps.
Edit. for all those looking for an example of working with AvalonEdit using MVVM, you can download a very basic editor application from http://1drv.ms/1E5nhCJ.
Notes. This application actually creates a MVVM friendly editor control by inheriting from the AvalonEdit standard control and adds additional Dependency Properties to it as appropriate - *this is different to what I have shown in the answer given above*. However, in the solution I have also shown how this can be done (as I describe in the answer above) using Attached Properties and there is code in the solution under the Behaviors namespace. What is actually implemented however, is the first of the above approaches.
Please also be aware that there is some code in the solution that is unused. This *sample* was a stripped back version of a larger application and I have left some code in as it could be useful to the user who downloads this example editor. In addition to the above, in the example code I access the Text by binding to document, there are some purest that may argue that this is not pure-MVVM, and I say "okay, but it works". Some times fighting this pattern is not the way to go.
I hope this of use to some of you.
I have a class which implements INotifyPropertyChanged. An instance of this class is declared as a DependencyProperty in a Window, e.g.,
public IMyClass MyClass
{
get { return (IMyClass)GetValue(MyClassProperty); }
set { SetValue(MyClassProperty, value); }
}
public static readonly DependencyProperty MyClassProperty=
DependencyProperty.Register("MyClass", typeof(IMyClass), typeof(MainWindow), new UIPropertyMetadata(null));
In the XAML, I have an element which is bound to this class using
Text="{Binding MyClass, Converter={StaticResource someConverter}}
Whenever I change a property in MyClass, I would like someConverter to be triggered. However, it only happens when I completely swap out MyClass. Is there a way to tie DependencyProperty updates to my MyClass PropertyChanged?
Update. In the spirit of AresAvatar's solution, here's what we have so far. The issue remaining is how to call InvalidateProperty (without having MyClass track it...)
public IMyClass MyClass
{
get { return (IMyClass)GetValue(MyClassProperty); }
set { SetValue(MyClassProperty, value); }
}
public static readonly DependencyProperty MyClassProperty =
DependencyProperty.Register("MyClass", typeof(IMyClass), typeof(MainWindow),
new UIPropertyMetadata(null, new PropertyChangedCallback(OnMyClassChanged)));
private static void OnMyClassChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue != null)
{
((IMyClass)e.OldValue).PropertyChanged -= ((MainWindow)d).MyClass_PropertyChanged;
}
if (e.NewValue != null)
{
((IMyClass)e.NewValue).PropertyChanged += ((MainWindow)d).MyClass_PropertyChanged;
}
}
private void MyClass_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
this.InvalidateProperty(MyClassProperty); <----- still does not refresh binding, but called.
}
Converters should not do more work than simple conversions, your question sounds like the converter uses a lot of properties of the object to create some combined value. Use a MultiBinding instead which hooks into all the different properties on the object you need, that way the MultiValueConverter on that MultiBinding will fire if any of those properties change.
Further, since you seem to create text you might be able to get away without using any converter at all as the StringFormat might be enough.
The only technique I've found is to call the binding's UpdateSource method in a strategically placed event handler, such as LostFocus.
private void mycontrol_LostFocus(object sender, RoutedEventArgs e)
{
if (mycontrol.IsModified)
{
var binding = mycontrol.GetBindingExpression(MyControl.FooBarProperty);
binding.UpdateSource();
}
}
If you don't care about chattiness or if your control doesn't take input focus, you could do this in mycontrol_PropertyChanged event or similar. However, forcing a conversion cycle on every property change or every keystroke may interfere with validation.
In MyClass, implement a NotifyPropertyChanged event. Then add a property changed callback to your MyClass DependencyProperty. In the DP's property changed callback, hook your new MyClass NotifyPropertyChanged event to a second callback function (and unhook the previous value, if any, with a -= operator). In the second callback function, call DependencyObject.InvalidateProperty so that the binding gets updated.
Edit: you may need to trigger a binding update with:
BindingExpressionBase exp = BindingOperations.GetBindingExpressionBase(this, Container.MyClassProperty);
if (exp != null)
exp.UpdateTarget();
class MyClass : INotifyPropertyChanged
{
/// <summary>
/// Event raised when a property is changed
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the property changed event
/// </summary>
/// <param name="e">The arguments to pass</param>
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
/// <summary>
/// Notify for property changed
/// </summary>
/// <param name="name">Property name</param>
protected void NotifyPropertyChanged(string name)
{
OnPropertyChanged(new PropertyChangedEventArgs(name));
}
/// <summary>
/// The parent container object
/// </summary>
public Container Parent { get; set; }
// Some data
int x;
}
class Container : DependencyObject
{
public static readonly DependencyProperty MyClassProperty = DependencyProperty.Register("MyClass", typeof(MyClass), typeof(Container), new FrameworkPropertyMetadata(MyClassPropChanged));
public MyClass MyClass
{
get { return (MyClass)GetValue(MyClassProperty); }
set { SetValue(MyClassProperty, value); }
}
void MyClassPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Container ct = d as Container;
if (ct == null)
return;
MyClass oldc = e.OldValue as MyClass;
if (oldc != null)
{
oldc.PropertyChanged -= new PropertyChangedEventHandler(MyClass_PropertyChanged);
oldc.Parent = null;
}
MyClass newc = e.NewValue as MyClass;
if (newc != null)
{
newc.Parent = ct;
newc.PropertyChanged += new PropertyChangedEventHandler(MyClass_PropertyChanged);
}
}
void MyClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
MyClass mc = sender as MyClass;
if (mc == null || mc.Parent == null)
return;
mc.Parent.InvalidateProperty(Container.MyClassProperty);
}
}
I have a combobox which binds to a collection of Foo on my viewmodel (FooCollection). I also set the SelectedItem property of the combobox to a property on my viewmodel of type Foo called SelectedFoo
I then set FooCollection and SelectedFoo and fire the appropriate OnPropertyChanged events.
My combobox contains the list of Foo, but the item displayed in the combobox is always the first item in the list. However, if you drop down the combobox, the item that is then highlighted is the correct item (SelectedFoo). Therefore, it is selecting the correct item, but not displaying it.
<ComboBox Grid.Row="5" ItemsSource="{Binding Path=FooCollection}"
SelectedItem="{Binding SelectedFoo, Mode=TwoWay}"
Name="FooSelectionControl"/>
Does anyone know how to fix this?
Hmm, it works on my end. What kind of collection are you using?
<ComboBox
SelectedItem="{Binding SelectedFoo, Mode=TwoWay}"
ItemsSource="{Binding FooCollection}">
</ComboBox>
Code behind:
public MainWindow()
{
InitializeComponent();
DataContext = this;
FooCollection = new BindingList<Foo>();
var foo = new Foo("Alpha");
FooCollection.Add(foo);
foo = new Foo("Beta");
SelectedFoo = foo;
FooCollection.Add(foo);
foo = new Foo("Gamma");
FooCollection.Add(foo);
}
public Foo SelectedFoo
{
get { return (Foo)GetValue(SelectedFooProperty); }
set { SetValue(SelectedFooProperty, value); }
}
public static readonly DependencyProperty SelectedFooProperty =
DependencyProperty.Register("SelectedFoo", typeof(Foo), typeof(MainWindow), new UIPropertyMetadata(null));
public BindingList<Foo> FooCollection
{
get { return (BindingList<Foo>)GetValue(FooCollectionProperty); }
set { SetValue(FooCollectionProperty, value); }
}
public static readonly DependencyProperty FooCollectionProperty =
DependencyProperty.Register("FooCollection", typeof(BindingList<Foo>), typeof(MainWindow), new UIPropertyMetadata(new BindingList<Foo>()));
and class Foo,
public class Foo : INotifyPropertyChanged
{
public Foo(string name)
{
_name = name;
}
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
OnPropertyChanged("Name");
}
}
public override string ToString()
{
return Name;
}
#region INotifyPropertyChanged event
///<summary>
///Occurs when a property value changes.
///</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event for
/// a given property.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
protected void OnPropertyChanged(string propertyName)
{
//validate the property name in debug builds
VerifyProperty(propertyName);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// Verifies whether the current class provides a property with a given
/// name. This method is only invoked in debug builds, and results in
/// a runtime exception if the <see cref="OnPropertyChanged"/> method
/// is being invoked with an invalid property name. This may happen if
/// a property's name was changed but not the parameter of the property's
/// invocation of <see cref="OnPropertyChanged"/>.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
[Conditional("DEBUG")]
private void VerifyProperty(string propertyName)
{
Type type = GetType();
//look for a *public* property with the specified name
PropertyInfo pi = type.GetProperty(propertyName);
if (pi == null)
{
//there is no matching property - notify the developer
string msg = "OnPropertyChanged was invoked with invalid property name {0}: ";
msg += "{0} is not a public property of {1}.";
msg = String.Format(msg, propertyName, type.FullName);
Debug.Fail(msg);
}
}
#endregion
}
Maybe try SelectedValue instead of SelectedItem. Also, make sure that Foo.Equals() is implemented correctly.