I have the following code, which is part of a work-around for binding null values to a ComboBox. (Let's assume I'm not supportive of changing this implementation).
SetBinding(ComboBox.SelectedValueProperty, _cachedBinding);
The question is: does this line of code lead to Source updating Target and vice-versa?
So if I have this property in the view model:
string MyProperty
{
get { return _myProperty; }
set
{
myProperty = value;
IsDirty = true;
}
}
then the setter will be called when I call SetBinding. That will be troublesome since in the setter I'm also managing the `IsDirty property to keep track of changes. I can code:
string MyProperty
{
get { return _myProperty; }
set
{
if (myProperty != value)
{
myProperty = value;
IsDirty = true;
}
}
}
but I feel that would be inelegant since I have a lot of properties out there.
So my second question is (assuming the answer to first question is yes), can I force Silverlight to not update Target or Source after SetBinding() (so they would update only when they are changed)?
How this workaraound works: http://surrealization.com/sample-code/fixing-combobox-selectedvalue-bug-with-expression-behaviors/. _cachedBinding thus contains the binding that was set before MyProperty became null and the binding disappeared (so it's the same binding that was there (not same as an object though, since it was created anew)).
Looking at the code once again, that might be even the same object, but it depends on how GetBindingExpression and ParentBinding work in .NET.
Here is the fully binded Combobox subclass:
public class ComboBoxFixedBinding : ComboBox
{
#region Events
private void ComboBoxEx_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (SelectedValue == null)
{
if (_cachedBinding != null && GetExistingBinding() == null)
{
SetBinding(ComboBox.SelectedValueProperty, _cachedBinding);
}
}
else
{
CacheExistingBinding();
}
}
private void CacheExistingBinding()
{
Binding binding = GetExistingBinding();
if (binding != null)
{
_cachedBinding = binding;
}
}
private Binding GetExistingBinding()
{
BindingExpression bindingExpr = this.GetBindingExpression(ComboBox.SelectedValueProperty);
if (bindingExpr == null)
{
return null;
}
else
{
return bindingExpr.ParentBinding;
}
}
#endregion
public ComboBoxFixedBinding()
{
SelectionChanged += ComboBoxEx_SelectionChanged;
}
private Binding _cachedBinding;
}
Related
I have a WPF application using MVVM. I have the IsChecked value bound to a boolean on my model instance on my ViewModel. I also need to bind a method on the ViewModel to the Checked and Unchecked events. (This is so I can track unsaved changes and change the background to give my users visual indication of the need to save. I tried:
<CheckBox
Content="Enable"
Margin="5"
IsChecked="{Binding Enabled}"
Checked="{Binding ScheduleChanged}"
Unchecked="{Binding ScheduleChanged}"
/>
But I get a 'Provide value on 'System.Windows.Data.Binding' threw an exception.' error. Advice?
Here is the Model I am working with:
public class Schedule : IEquatable<Schedule>
{
private DateTime _scheduledStart;
private DateTime _scheduledEnd;
private bool _enabled;
private string _url;
public DateTime ScheduledStart
{
get { return _scheduledStart; }
set
{
_scheduledStart = value;
}
}
public DateTime ScheduledEnd
{
get { return _scheduledEnd; }
set
{
if(value < ScheduledStart)
{
throw new ArgumentException("Scheduled End cannot be earlier than Scheduled Start.");
}
else
{
_scheduledEnd = value;
}
}
}
public bool Enabled
{
get { return _enabled; }
set { _enabled = value; }
}
public string Url
{
get { return _url; }
set { _url = value; }
}
public bool Equals(Schedule other)
{
if(this.ScheduledStart == other.ScheduledStart && this.ScheduledEnd == other.ScheduledEnd
&& this.Enabled == other.Enabled && this.Url == other.Url)
{
return true;
}
else
{
return false;
}
}
}
My viewModel contains a property that has an ObservableCollection. An ItemsControl binds to the collection and generates a list. So my ViewModel sort of knows about my Model instance, but wouldn't know which one, I don't think.
Checked and Unchecked are events, so you can not bind to them like you can IsChecked, which is a property. On a higher level it is also probably wise for your view model not to know about a checkbox on the view.
I would create an event on the view model that fires when Enabled is changed, and you can subscribe to that and handle it any way you like.
private bool _enabled;
public bool Enabled
{
get
{
return _enabled;
}
set
{
if (_enabled != value)
{
_enabled = value;
RaisePropertyChanged("Enabled");
if (EnabledChanged != null)
{
EnabledChanged(this, EventArgs.Empty);
}
}
}
}
public event EventHandler EnabledChanged;
// constructor
public ViewModel()
{
this.EnabledChanged += This_EnabledChanged;
}
private This_EnabledChanged(object sender, EventArgs e)
{
// do stuff here
}
You should be able to just handle this in the setter for Enabled...
public class MyViewModel : ViewModelBase
{
private bool _isDirty;
private bool _enabled;
public MyViewModel()
{
SaveCommand = new RelayCommand(Save, CanSave);
}
public ICommand SaveCommand { get; }
private void Save()
{
//TODO: Add your saving logic
}
private bool CanSave()
{
return IsDirty;
}
public bool IsDirty
{
get { return _isDirty; }
private set
{
if (_isDirty != value)
{
RaisePropertyChanged();
}
}
}
public bool Enabled
{
get { return _enabled; }
set
{
if (_enabled != value)
{
_enabled = value;
IsDirty = true;
}
//Whatever code you need to raise the INotifyPropertyChanged.PropertyChanged event
RaisePropertyChanged();
}
}
}
You're getting a binding error because you can't bind a control event directly to a method call.
Edit: Added a more complete example.
The example uses the MVVM Lite framework, but the approach should work with any MVVM implementation.
I find myself quite often in the following situation:
I have a user control which is bound to some data. Whenever the control is updated, the underlying data is updated. Whenever the underlying data is updated, the control is updated. So it's quite easy to get stuck in a never ending loop of updates (control updates data, data updates control, control updates data, etc.).
Usually I get around this by having a bool (e.g. updatedByUser) so I know whether a control has been updated programmatically or by the user, then I can decide whether or not to fire off the event to update the underlying data. This doesn't seem very neat.
Are there some best practices for dealing with such scenarios?
EDIT: I've added the following code example, but I think I have answered my own question...?
public partial class View : UserControl
{
private Model model = new Model();
public View()
{
InitializeComponent();
}
public event EventHandler<Model> DataUpdated;
public Model Model
{
get
{
return model;
}
set
{
if (value != null)
{
model = value;
UpdateTextBoxes();
}
}
}
private void UpdateTextBoxes()
{
if (InvokeRequired)
{
Invoke(new Action(() => UpdateTextBoxes()));
}
else
{
textBox1.Text = model.Text1;
textBox2.Text = model.Text2;
}
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
model.Text1 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void textBox2_TextChanged(object sender, EventArgs e)
{
model.Text2 = ((TextBox)sender).Text;
OnModelUpdated();
}
private void OnModelUpdated()
{
DataUpdated?.Invoke(this, model);
}
}
public class Model
{
public string Text1 { get; set; }
public string Text2 { get; set; }
}
public class Presenter
{
private Model model;
private View view;
public Presenter(Model model, View view)
{
this.model = model;
this.view = view;
view.DataUpdated += View_DataUpdated;
}
public Model Model
{
get
{
return model;
}
set
{
model = value;
view.Model = model;
}
}
private void View_DataUpdated(object sender, Model e)
{
//This is fine.
model = e;
//This causes the circular dependency.
Model = e;
}
}
One option would be to stop the update in case the data didn't change since the last time. For example if the data were in form of a class, you could check if the data is the same instance as the last time the event was triggered and if that is the case, stop the propagation.
This is what many MVVM frameworks do to prevent raising PropertyChanged event in case the property didn't actually change:
private string _someProperty = "";
public string SomeProperty
{
get
{
return _someProperty;
}
set
{
if ( _someProperty != value )
{
_someProperty = value;
RaisePropertyChanged();
}
}
}
You can implement this concept similarly for Windows Forms.
What you're looking for is called Data Binding. It allows you to connect two or more properties, so that when one property changes others will be updated auto-magically.
In WinForms it's a little bit ugly, but works like a charm in cases such as yours. First you need a class which represents your data and implements INotifyPropertyChanged to notify the controls when data changes.
public class ViewModel : INotifyPropertyChanged
{
private string _textFieldValue;
public string TextFieldValue {
get
{
return _textFieldValue;
}
set
{
_textFieldValue = value;
NotifyChanged();
}
}
public void NotifyChanged()
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Than in your Form/Control you bind the value of ViewModel.TextFieldValue to textBox.Text. This means whenever value of TextFieldValue changes the Text property will be updated and whenever Text property changes TextFieldValue will be updated. In other words the values of those two properties will be the same. That solves the circular loops issue you're encountering.
public partial class Form1 : Form
{
public ViewModel ViewModel = new ViewModel();
public Form1()
{
InitializeComponent();
// Connect: textBox1.Text <-> viewModel.TextFieldValue
textBox1.DataBindings.Add("Text", ViewModel , "TextFieldValue");
}
}
If you need to modify the values from outside of the Form/Control, simply set values of the ViewModel
form.ViewModel.TextFieldValue = "new value";
The control will be updated automatically.
You should look into MVP - it is the preferred design pattern for Winforms UI.
http://www.codeproject.com/Articles/14660/WinForms-Model-View-Presenter
using that design pattern gives you a more readable code in addition to allowing you to avoid circular events.
in order to actually avoid circular events, your view should only export a property which once it is set it would make sure the txtChanged_Event would not be called.
something like this:
public string UserName
{
get
{
return txtUserName.Text;
}
set
{
txtUserName.TextChanged -= txtUserName_TextChanged;
txtUserName.Text = value;
txtUserName.TextChanged += txtUserName_TextChanged;
}
}
or you can use a MZetko's answer with a private property
So I am trying to implement a custom binding for a UITextField in MvvmCross, pretty much along the lines of Binding 'GO' key on Software Keyboard - i.e. trying to bind a text field to automatically fire an event when the Done button is tapped on the keyboard (so binding to ShouldReturn). I also need to bind the text field's EditingDidBegin and EditingDidEnd events. Because I am binding more than one event, I have created a MvxPropertyInfoTargetBinding as follows:
public class MyTextFieldTargetBinding : MvxPropertyInfoTargetBinding<UITextField>
{
private ICommand _command;
protected UITextField TextField
{
get { return (UITextField)Target; }
}
public MyTextFieldTargetBinding(object target, PropertyInfo targetPropertyInfo) : base(target, targetPropertyInfo)
{
TextField.ShouldReturn += HandleShouldReturn;
TextField.EditingDidBegin += HandleEditingDidBegin;
TextField.EditingDidEnd += HandleEditingDidEnd;
}
private bool HandleShouldReturn(UITextField textField)
{
if (_command == null) {
return false;
}
var text = textField.Text;
if (!_command.CanExecute (text)) {
return false;
}
textField.ResignFirstResponder();
_command.Execute(text);
return true;
}
private void HandleEditingDidBegin (object sender, EventArgs e)
{
// do something
}
private void HandleEditingDidEnd (object sender, EventArgs e)
{
// do something
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
public override void SetValue(object value)
{
var command = value as ICommand;
_command = command;
}
public override Type TargetType
{
get { return typeof(ICommand); }
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
if (TextField != null)
{
TextField.ShouldReturn -= HandleShouldReturn;
TextField.EditingDidBegin -= HandleEditingDidBegin;
TextField.EditingDidEnd -= HandleEditingDidEnd;
}
}
base.Dispose(isDisposing);
}
}
My first question is: am I correct in creating one MvxPropertyInfoTargetBinding for all the events? Relatedly, I don't get the difference between MvxPropertyInfoTargetBinding and MvxTargetBinding. According to MVVMCross Binding decimal to UITextField removes decimal point the former is used when replacing an existing binding, the latter for known properties and event pairs. So am I using the correct one?
Secondly (and the real crux of my problem), my code works except for SetValue - it is fired, but the value is null. Here is what I have in my Setup file:
protected override void FillTargetFactories (IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories (registry);
registry.RegisterPropertyInfoBindingFactory(typeof(MyTextFieldTargetBinding), typeof(UITextField), "Text");
}
I don't do anything in my View - perhaps that is where the issue lies?
EDIT:
My ViewModel:
public class LoginViewModel : MvxViewModel
{
private string _username;
public string Username
{
get { return _username; }
set { _username = value; RaisePropertyChanged(() => Username); }
}
private string _password;
public string Password
{
get { return _password; }
set { _password = value; RaisePropertyChanged(() => Password); }
}
private MvxCommand _login;
public ICommand Login
{
get {
_login = _login ?? new MvxCommand(DoLogin);
return _login;
}
}
public LoginViewModel(ILoginManager loginManager)
{
_loginManager = loginManager;
}
private void DoLogin()
{
// call the login web service
}
}
In my `View', I don't do anything fancy (I do create the View elements in a XIB):
public override void ViewDidLoad()
{
base.ViewDidLoad ();
this.NavigationController.SetNavigationBarHidden(true, false);
var set = this.CreateBindingSet<LoginView, Core.ViewModels.LoginViewModel>();
set.Bind(usernameTextField).To(vm => vm.Username);
set.Bind(passwordTextField).To(vm => vm.Password);
set.Bind (loginButton).To (vm => vm.Login);
set.Apply();
}
No interesting Trace messages.
1. What is special about PropertyInfoTargetBinding?
The question you reference - MVVMCross Binding decimal to UITextField removes decimal point - gives the key to the difference between MvxTargetBinding and MvxPropertyInfoTargetBinding:
TargetBinding can be used for any arbitrary binding - e.g. for a non-propertyInfo-based binding
PropertyInfoTargetBinding inherits from TargetBinding and can only be used with actual C# Properties - because it uses PropertyInfo via Reflection.
In your case, since you aren't actually using the Text property via Reflection, then I'd be tempted not to use a PropertyInfoTargetBinding and to steer clear of the Text name as well - instead just write a custom TargetBinding.
the former is used when replacing an existing binding
This is definitely not true - instead any binding can be used to replace another binding - as the answer on the other question says
MvvmCross operates a simple 'last registered wins' system
For more on custom bindings, take a look at:
the N=28 video in http://mvvmcross.blogspot.co.uk/
take a look through some of the "standard" bindings that ship with MvvmCross
Droid - https://github.com/MvvmCross/MvvmCross/tree/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Droid/Target
iOS - https://github.com/MvvmCross/MvvmCross/tree/v3.1/Cirrious/Cirrious.MvvmCross.Binding.Touch/Target
note that most of these use either MvxPropertyInfoTargetBinding or MvxConvertingTargetBinding as a base class
2. Why is my SetValue getting null?
Your current binding code is asking for an ICommand:
public override Type TargetType
{
get { return typeof(ICommand); }
}
But your View code is currently binding the View to a string:
// View
set.Bind(usernameTextField).To(vm => vm.Username);
// ViewModel
private string _username;
public string Username
{
get { return _username; }
set { _username = value; RaisePropertyChanged(() => Username); }
}
To solve this...
Work out what you want to bind to - is it an ICommand (e.g. and MvxCommand) or is it a string?
Change the View and the Binding to reflect this.
In my usercontrol.xaml.cs. I have this dependency proprerty as bleow.
public static readonly DependencyProperty MessageKeyProperty =
DependencyProperty.Register("MessageKey", typeof(String),
typeof(UC_MessageEntry),
new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(MessageKeyPropertyChangedCallback)));
private static void MessageKeyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UC_MessageEntry)
{
UC_MessageEntryucMessageEntryAccessDenied = (UC_MessageEntry)d;
if (e.NewValue != null)
{
ResourceBundle resourceBundle = App.ResourceBundle;
if (e.NewValue.ToString().Equals("enable"))
{
ucMessageEntryAccessDenied.txtAceessDeniedMsg.Text = "";
return;
}
String actualMessage = resourceBundle.GetString("Resources", e.NewValue.ToString());
if (actualMessage == null)
{
ucMessageEntryAccessDenied.txtAceessDeniedMsg.Text = resourceBundle.GetString("Resources", "ContractSetup.ExchangeAccessDeniedMessage.OTHERS");
}
else
{
ucMessageEntryAccessDenied.txtAceessDeniedMsg.Text = actualMessage;
}
}
else
{
ucMessageEntryAccessDenied.txtAceessDeniedMsg = null;
}
}
}
public String MessageKey
{
get
{
return (String)this.GetValue(MessageKeyProperty);
}
set
{
this.SetValue(MessageKeyProperty,value);
}
}
In mainwindow.xaml , i bind this MessageKey as below.
<view_MessageEntry:UC_MessageEntry
x:Uid="local:UC_MessageEntry_1" x:Name="UC_OrderEntry" MessageKey="{Binding MsgAccessDenied}"
Style="{DynamicResource contentControlStyle}" SnapsToDevicePixels="True" Margin="0" />
And Behind MessageViewModle.cs,
private static readonly PropertyChangedEventArgs MsgAccessDeniedPropertyChangedEventArgs
= new PropertyChangedEventArgs("MsgAccessDenied");
private string _msgAccessDenied;
public string MsgAccessDenied
{
get
{
if (_selectedExchange != null)
{
return _msgAccessDenied;
}
else
{
return "enable";
}
}
set
{
_msgAccessDenied = value;
this.RaisePropertyChanged(this, MsgAccessDeniedPropertyChangedEventArgs);
}
}
public void FireMsg()
{
this.MsgAccessDenied="value";
}
When the combo box selection is changed, I called FireMsg() and it will update the MessageKeyPropertyChangedCallback function in usercontrol.xaml.cs. It's working fine. But If I call this FireMsg() from Other ViewModels, value of _msgAccessDenied is updated. But MessageKeyPropertyChangedCallback function is not firing. Any solution for this issue? Thanks.
Your code looks generally sound. But if your dependency property change callback is not being called, it is almost certainly because the value of the dependency property isn't changing. And if the dependency property is bound to a source, then it is probably because the source isn't changing.
From my quick review of the code, the only way that I can see that happening is:
the value of MsgAccessDenied is set
this causes the field _msgAccessDenied to be set
the RaisePropertyChanged method is called to trigger PropertyChanged
the dependency subsystem does its thing which forces a get of MsgAccessDenied
the MsgAccessDenied getter is called
the getter checks _selectedExchange and it is null
the getter returns the value "enable" instead of the new value of _msgAccessDenied
the previous value was also "enable"
the dependency subsystem says, OK, no change
the property change callback is not called
In summary, _selectedExchange can hide value changes in _msgAccessDenied, thereby preventing the upstream change callback from firing.
This is just a theory.
I just created a control with the following code:
public partial class KindsEditor : NaviGroupList, INotifyPropertyChanged
{
private WebBrowser _Browser;
private BasicProject _Project;
public event PropertyChangedEventHandler PropertyChanged;
public bool RequiredDataLoaded { get { return (Project != null) && (Browser != null); } }
private bool _ButtonsEnabled = false;
public bool ButtonsEnabled { set { SetButtonsEnabled(value); } get { return _ButtonsEnabled; } }
public WebBrowser Browser
{
get { return _Browser; }
set
{
_Browser = value;
OnPropertyChanged(new PropertyChangedEventArgs("Browser"));
OnPropertyChanged(new PropertyChangedEventArgs("RequiredDataLoaded"));
}
}
public BasicProject Project
{
get { return null; }
set { LoadProject(value); }
}
public KindsEditor()
{
InitializeComponent();
DataBindings.Add("ButtonsEnabled", this, "RequiredDataLoaded");
}
private void SetButtonsEnabled(bool value)
{
newKindButton.Enabled = value;
_ButtonsEnabled = value;
OnPropertyChanged(new PropertyChangedEventArgs("ButtonsEnabled"));
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, e);
}
private void LoadProject(BasicProject value)
{
if (value != null) DataSource = value.Kinds;
_Project = value;
OnPropertyChanged(new PropertyChangedEventArgs("Project"));
OnPropertyChanged(new PropertyChangedEventArgs("RequiredDataLoaded"));
}
}
I removed some stuff that I think is irrelevant to my problem. I was trying to bind one button (newKindButton) being enabled to two properties (Browser and Project) being not null. I know it's messy and no one would expect that I call OnPropertyChanged while changing a different property and other stuff that might not be supposed to be done. I'll fix that later. But the weird thing is that the Form that uses this control (I drag and dropped it from the toolbox) added this line to the InitializeComponent() auto-generated code:
this.kindsEditor1.DataBindings.Add(new System.Windows.Forms.Binding("ButtonsEnabled", this.kindsEditor1, "RequiredDataLoaded", true));
So when I try to run the app I get an exception telling me that this line is trying to bind to the same property twice. I swear, I never added any binding from the properties panel. If I remove the line
DataBindings.Add("ButtonsEnabled", this, "RequiredDataLoaded");
from KindsEditor's constructor, the auto-generated line disappears. Anyone knows what's going on?
Try adding DesignerProperties.GetIsInDesignMode around the binding:
public KindsEditor()
{
InitializeComponent();
if (!DesignerProperties.GetIsInDesignMode(this))
DataBindings.Add("ButtonsEnabled", this, "RequiredDataLoaded");
}
I don't have a direct answer, but I suspect that Visual Studio thinks it needs to serialize something (the generated code) when it shouldn't. The above construct hides the binding from Visual Studio and only activates it during runtime.