I need to bind a property to a ToolStripMenuItem. I've searched around and found it impossible, the best workaround seems to be creating a BindableToolStripMenuItem class and implement it yourself. So I've taken some reasonably well established code from the internet:
public class BindableToolStripMenuItem : ToolStripMenuItem,IBindableComponent
{
private BindingContext bindingContext;
private ControlBindingsCollection dataBindings;
[Browsable(false)]
public BindingContext BindingContext
{
get
{
if (bindingContext == null)
bindingContext = new BindingContext();
return bindingContext;
}
set
{
bindingContext = value;
}
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ControlBindingsCollection DataBindings
{
get
{
if (dataBindings == null)
dataBindings = new ControlBindingsCollection(this);
return dataBindings;
}
}
}
Now all I need is to bind it right? I set up a form with a toolStripMenu and added a property to bind to: No luck. I can check it, but the changes don't seem to filter down into the binding.
public partial class Form1 : Form
{
private Boolean _BindingChecked;
public Boolean BindingChecked {
get { return _BindingChecked; }
set { _BindingChecked = value; Console.WriteLine(": " + _BindingChecked); }
}
public Form1()
{
InitializeComponent();
BindableToolStripMenuItem btsmi = new BindableToolStripMenuItem();
btsmi.Text = "Checkable";
btsmi.CheckOnClick = true;
btsmi.DataBindings.Add(new Binding("Checked",this,"BindingChecked"));
itemsToolStripMenuItem.DropDownItems.Add(btsmi);
}
}
It's definitely doing some of the binding though! If I change "Binding Checked" or "Checked" strings it throws the appropriate error eg:
An unhandled exception of type 'System.ArgumentException' occurred in System.Windows.Forms.dll
Additional information: Cannot bind to the property or column BindingChecked2 on the DataSource.
It's just any changes (to either, I tested it implementing InotifyChanged going the other way too) don't get applied to the bound property.
Any Idea where I've gone wrong? I've cut things down so much, it's just these two classes, and I've tried starting from scratch with the BdindableToolStripMenuItem, and looked at a fair few other people's implementation (almost all identical) and still can't ever get it to work.
Thanks
You probably want to set the DataSourceUpdateMode of the Binding to OnPropertyChanged, the default is OnValidation but since menu items don't have normal focus behavior I can imagine the default does not work. This allows updates on the menu item to propagate to the BindingChecked property
If you want the reverse, updates to BindingChecked property by code to propagate to the menu item, you must implement INotifyPropertyChanged on the form and raise the event whenever you change the property from code (e.g. in the property set accessor).
Related
I have a user control with a dependency property:
public ObservableCollection<Exclusion> SelectedExclusions
{
get
{
return (ObservableCollection<Exclusion>)GetValue(SelectedExclusionsProperty);
}
set
{
SetValue(SelectedExclusionsProperty, value);
}
}
public static readonly DependencyProperty SelectedExclusionsProperty =
DependencyProperty.Register(nameof(TimeSeriesChart.SelectedExclusions),
typeof(ObservableCollection<Exclusion>),
typeof(TimeSeriesChart),
new PropertyMetadata(default(ObservableCollection<Exclusion>)));
I am adding a selected exclusion to this collection on key down:
protected override void OnKeyDown(KeyEventArgs e)
{
if(e.Key == Key.Delete)
{
this.SelectedExclusions.Add(this.ExclusionProviders[0].Exclusions[this.hitTestInfo.DataSeriesIndex]);
}
}
In the view model I have this property & backing variable:
private ObservableCollection<TimeSeriesLibraryInterop.Exclusion> selectedExclusionsToDelete = new ObservableCollection<TimeSeriesLibraryInterop.Exclusion>();
public ObservableCollection<TimeSeriesLibraryInterop.Exclusion> SelectedExclusionsToDelete
{
get
{
return this.selectedExclusionsToDelete;
}
set
{
this.selectedExclusionsToDelete = value;
this.RaisePropertyChanged();
}
}
Finally the binding in the view:
<userControl1 SelectedExclusions="{Binding SelectedExclusionsToDelete, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The dependency property collection is initialised and populated however the view model property setter is never hit when the dependency property collection changes (Add). I have no binding errors in the output window. Is there something I'm missing here?
Looks like you're adding an item to the collection rather than replacing the collection. You won't hit the vm collection property's setter that way.
If you want to your viewmodel to respond to items being added to the SelectedExclusionsToDelete collection, the viewmodel will need to handle the SelectedExclusionsToDelete.CollectionChanged event. "Properly" handling that event (remove, add, move, clear, etc.) is a real hassle, but if it's not a giant collection you can often get away with something quick and dirty: Treat any change as a whole new collection. I think that's exactly the case you've got, too.
Alternatively, for an even quicker and dirtier approach, I think you could make it a two-way binding by default and have the control assign a new ObservableCollection to this.SelectedExclusions in OnKeyDown. The binding will pass it back to the viewmodel and hit the setter.
I have a generic custom control that contains a property as seen below:
public T Editing
{
get { return _editing; }
set
{
if (object.ReferenceEquals(value, _editing))
return;
BeginInvoke(new MethodInvoker(() => UpdateOnGuiThread(value)));
}
}
I have tried databinding this property to a property on my controller object as seen below:
_customCtrl.DataBindings.Add(new Binding("Editing", _controller, "CurrentItem"));
The controller class implements INotifyPropertyChanged and exposes the property like this:
public SpecialData CurrentItem
{
get { return _currentItem; }
set
{
_currentItem= value;
OnPropertyChanged("CurrentItem");
}
}
But as I broadcast PropertyChanged from my controller class the debugger will never enter in the setter of the Editing property. I've also tried the databinding below to no avail.
_customCtrl.DataBindings.Add(new Binding("Editing", _controller, "CurrentItem", false, DataSourceUpdateMode.OnPropertyChanged));
I've read https://msdn.microsoft.com/en-us/library/ms233813(v=vs.80).aspx and tried using the DefaultBindingPropertyAttribute but it doesn't help either.
Does anybody know how to get this binding to work? I thought that the binding manager would propagate the value from the controller to the control's property as PropertyChanged is broadcast in the controller class (just like a simple textbox text binding).
Any ideas?
Thanks,
Sean
You should always create your bindings with FormattingEnabled property set to true. Note that FormattingEnabled=false is just for backward compatibility and unfortunately is the default.
Either use one of the Binding constructor (or ControlBindingsCollection.Add) overloads that have a formattingEnabled parameter or set the property afterwards. In your case, something like this
_customCtrl.DataBindings.Add("Editing", _controller, "CurrentItem", true);
Recently I've discovered a strange behavior when I assign a ViewModel which comes back from a WCF service call to a DataContext in a UserControl element. When I first assign the ViewModel everything is fine. All XAML bindings are showing the correct and expected values if available.
But let's say I've saved changes in the ViewModel via another service call and get back an updated version of the ViewModel and now like to reassign the new ViewModel to the DataContext sometimes the DataContext and the ViewModel (which has been assigned) are losing attribute values (they are null after assigning).
Let me show you a simplified example to demonstrate
namespace MyProject.ViewModels
{
[DataContract]
public class MyViewModel : ViewModelBase //ViewModelBase implements INotifyPropertyChanged
{
#region Properties
private MyViewModelListItem _myViewModelListItem; //another nested ViewModel
[DataMember]
public MyViewModelListItem myViewModelListItem { get { return _myViewModelListItem; } set { _myViewModelListItem = value; RaisePropertyChanged(() => myViewModelListItem); } }
private string _aStringVariable; //just a simple string
[DataMember]
public string aStringVariable { get { return _aStringVariable; } set { _aStringVariable = value; RaisePropertyChanged(() => aStringVariable); } }
private MyEnum _myEnum; //an enumeration
[DataMember]
public MyEnum myEnum { get { return _myEnum; } set { _myEnum = value;RaisePropertyChanged(() => myEnum); } }
#endregion
}
}
Here is an extract of a simplified code behind of an xaml UserControl
//some other code parts not related to the problem
// get the VM
MyViewModel vm = service.getMyViewModel();
if(vm != null){
//assign the datacontext
userControl.Grid.DataContext = vm; //everything is fine. all values are set as expected. Databinding works like a charm
}
Now let's imagine some changes in the vm via user input and data binding. The viewmodel is saved (calling a WCF service method) and after that I call the service again to get an updated version of the VM:
// get the VM again
vm = service.getMyViewModel();
if(vm != null){
//assign the datacontext
userControl.Grid.DataContext = vm;
}
When I set a breakpoint before the assignment of the new ViewModel all values of any attribute of the ViewModel are set (because the service filled them with values)
But then right after the assignment some of the attribute values are null. I cannot say which one. I'm using a lot of different ViewModels in my project and in this example maybe the string is not null and the nested ViewModel also. But the enumeration is suddenly null. And at another place in the project maybe something else is suddenly null.
I assume that the DataContext variable has a setter method which is doing something strange (or I get it totally wrong).
Does anybody know what's happening here? And in addition what could be a "good" way of preventing this (maybe reassigning is the wrong approach).
For now I just set the DataContext to null before reassigning the ViewModel but it seems not the best way, does it?
vm = service.getMyViewModel();
if(vm != null){
//assign the datacontext
userControl.Grid.DataContext = null;
userControl.Grid.DataContext = vm;
}
Edit:
just to clarify: No viewmodels are saved into a database. They are properly converted to corresponding entities and then these are saved.
changed this to userControl.Grid to be more specific in the examples
I have a MVVM Windows Phone 8 app. The XAML page has a user control that I created that needs to be notified when a change takes place in the View Model. To facilitate this, I created an int property in the user control to be bound to a property in the View Model, so the user control property's setter method would be triggered when the property it was bound to in the View Model changed.
Using the code below, the user control's VideosShownCount property does show up in the Property List at design-time but when I click on the binding mini-button, the Create Data Binding option is greyed out in the pop-up menu.
So I have one or two questions, depending on what is the root problem:
1) How do I make a property in a View Model available as a Data Binding source?
2) How do I format a user control property so the IDE allows it to be data bound to a View Model property?
private int _videosShownCount = 0;
public int VideosShownCount
{
get
{
return this._videosShownCount;
}
set
{
this._videosShownCount = value;
}
}
public static readonly DependencyProperty VideoShownCountProperty =
DependencyProperty.Register("VideosShownCount", typeof(int), typeof(MyUserControl),
new PropertyMetadata(0, new PropertyChangedCallback(VideoShownCountPropertyChanged)));
static void VideoShownCountPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MyUserControl MyUserUserControl = (MyUserControl)sender;
// Don't care about the value, just want the notification.
// int val = (int)e.NewValue;
// Do work now that we've been notified of a change.
MyUserUserControl.DoWork();
}
You're not using the DependencyProperty for your property, which will definitely cause problems between your code and the bindings
public int VideosShownCount
{
get { return (int) GetValue(VideosShownCountProperty); }
set { SetValue(VideosShownCountProperty, value); }
}
I'm not sure if this is the main cause of your problem, but it's worth fixing regardless.
I am trying to come up with a good way of implementing the MVVM pattern using Entity-Framework where my entities are my models. My DataContext is my viewmodel. This is a small reproduction of the problem.
View
<TextBox Text="{Binding MyText}" />
ViewModel:
I have the requirement of needing to navigate record by record from my DB. When a button is clicked in the View a command is sent to the Viewmodel that executes nextRecord(). EF does its magic and _myObject is the next row/record from the database
public class myViewModel: INotifyPropertyChanged
{
private MyEntityObject _myObject;
public string MyText
{
get { return _myObject.MyText; }
set
{
if (_myObject.MyText != value)
{
_myObject.MyText = value;
OnPropertyChanged("MyText");
}
}
}
private void _nextRecord()
{
_myObject = myEntitiesContext.NextRecord() //pseudocode
}
}
Autogenerated Entity Model
public partial class MyEntityObject
{
public string MyText { get; set; }
}
Since the View has no knowledge of _myObject changing, it doesn't update when _myObject changes. A few approaches I have thought of.
I haven't tested wrapping my entities in a INotifyPropertyChanged wrapper class but am wary to do this as I have a lot of entity objects.
I could call OnPropertyChanged("...") for all properties, but some of my entities have a lot of properties to them, which would be ugly. Possible to use reflection to make it cleaner, but I may have properties that aren't databound.
I might be able to defer this to the UI, somehow refreshing the bindings when I click "Next Record", but this breaks MVVM and looks dirty
How can I get the UI to recognize changes from _myObject?
As I've mentioned in the comments, calling OnPropertyChanged("") or OnPropertyChanged(null) invalidates all properties and is equivalent to calling OnPropertyChanged for each and every property. This behavior is also documented here:
The PropertyChanged event can indicate all properties on the object
have changed by using either null or String.Empty as the property name
in the PropertyChangedEventArgs.
This means that you can simply add a call to OnPropertyChanged("") when you update your object to force WPF to reevaluate all bindings to your view model:
private void _nextRecord()
{
_myObject = myEntitiesContext.NextRecord();
OnPropertyChanged("");
}
That being said, I'd still go with #Anand's solution (+1). There's an ongoing debate on whether it's OK or not for the viewmodel to expose the model as a property, and I tend to go with exposing it until you need to introduce some view model specific logic. Most of the time you won't have to and it's not worth the trouble of wrapping model properties.
The problem with your code is that when _myObject changes the MyText property changed event is not fired. A work around would be to create a new property to hold you entity
and make this property as your Grids DataContext in your view as shown below. Now when this line is executed MyObject = myEntitiesObject.NextRecord() your view will be notified about the change.
public class myViewModel : INotifyPropertyChanged
{
private MyEntityObject _myObject;
public MyEntityObject MyObject
{
get { return _myObject; }
set {
if (_myObject != value)
{
_myObject = value;
OnPropertyChanged("MyObject");
}
}
}
private void _nextRecord()
{
MyObject = myEntitiesObject.NextRecord() //pseudocode
}
}
View:
<Grid DataContext="{Binding MyObject}">
<TextBlock Text="{Binding MyText}"/>
</Grid>
An extremely simple but not very elegant solution that I believe would meet needs: upon switching records, set the DataContext to null, then back to the ViewModel.
However, there are arguably more elegant alternatives that require more work to meet all requirements. See Anand's answer for an improvement upon this.
The tag in View should have the mode and UpdateSourceTrigger attribute set with values.