Given I have this UserControl:
public class MyStringUserControl : UserControl
{
public string MyString
{
get { return (string)GetValue(MyStringProperty); }
set { SetValue(MyStringProperty, value); }
}
public static readonly DependencyProperty MyStringProperty =
DependencyProperty.Register("MyString", typeof(string), typeof(MyStringUserControl),
new FrameworkPropertyMetadata(null));
}
And this ViewModel:
public class MyStringViewModel
{
public string MyString { get; set; }
}
Now I use the MyStringUserControl in another View like this:
<controls:MyStringUserControl MyString="{Binding SomeStringProperty} />
I'am looking for an elegant way to bind this string back to the MyStringViewModel.
I also don't feel comfortable with the fact that I have to duplicate every property in the UserControl code behind and ViewModel. Is there a better way to do this?
Edit #1:
The reason I want to do this is because of unit testing (creating a UserControl takes very long even without InitializeComponent)
There is absolutely no point in duplicating your properties. Using MVVM does not mean that you need to have a view model for every UserControl. When I use a UserControl as a part of a view, I rarely use a separate view model for it. Sometimes, I'll just use the DependencyPropertys in the UserControl code behind, while other times I'll just data bind to the parent view model directly. It all depends on what you want to do with the data.
If I was intent on doing this, I would use DependencyProperty's PropertyChanged event handler to set my ViewModel's property, and my ViewModel's PropertyChanged event handler to set my DependencyProperty. Having said that I've never had a reason to go down this particular road.
private SomeViewModel _viewModel;
public static readonly DependencyProperty MyStringProperty = DependencyProperty.Register("MyString", typeof(string), typeof(MyStringUserControl), new PropertyMetadata(OnMyStringChanged));
public MyStringUserControl()
{
InitializeComponent();
_viewModel = new SomeViewModel();
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
this.DataContext = _viewModel;
}
private static void OnMyStringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyStringUserControl)d).OnMyStringChanged(e.NewValue);
}
private void OnMyStringChanged(string newValue)
{
_viewModel.SomeProperty = newValue;
}
private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "SomeProperty":
SetValue(MyStringProperty, _viewModel.SomeProperty);
break;
default:
break;
}
}
Related
I am working on a WPF application using reactiveui, and am having difficulty getting two way binding working in my custom dependency property. I can get this working using WPF binding but not using the reactiveui.
I have a user control called IPAddressControl, which takes in an IP address input and provides a dependency property ValidIpAddress, shown below.
public static readonly DependencyProperty ValidIpAddressProperty =
DependencyProperty.Register(
"ValidIpAddress",
typeof(bool),
typeof(IPAddressControl),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnValidIpAddressPropertyChanged)));
public bool ValidIpAddress
{
get
{
return (bool)GetValue(ValidIpAddressProperty);
}
set
{
SetValue(ValidIpAddressProperty, value);
}
}
private static void OnValidIpAddressPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IPAddressControl IPAddressControl = d as IPAddressControl;
IPAddressControl.ValidIpAddress = (bool)e.NewValue;
}
In my view, I set up the binding as follows :
public partial class MyView : ReactiveUserControl<MyViewModel>
public MyView()
{
InitializeComponent();
ViewModel = new MyViewModel();
this.WhenActivated(view =>
{
this.Bind(ViewModel, vm => vm.ValidIpAddress, v => v.ipaddress.ValidIpAddress);
});
}
And define my view model property
public class MyViewModel : ReactiveObject
{
bool _validIpAddress = false;
public bool ValidIpAddress
{
get => this._validIpAddress;
set => this.RaiseAndSetIfChanged(ref _validIpAddress, value);
}
}
When the property gets updated in IPAddressControl
IPAddressControl.ValidIPaddress = true;
I would expect MyViewModel.ValidIpAddress.Set to be called, however this is not happening.
When I revert to the standard WPF implementation, the two way binding works well, i.e. I set the binding in the myview.xaml
ValidIpAddress="{Binding Path=ValidIpAddress}"
then set the data context in constructor MyView();
this.DataContext = new MyViewModel()
So it appears I am doing something wrong in relation to the reactiveui binding.
I'd appreciate if someone could help.
Thanks.
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 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
In XAML, I have a TextBox with x:Name of MyTextBox.
<TextBox x:Name="MyTextBox">Some text</TextBox>
For speed reasons, I want to call the method .AppendText, e.g. In C# code behind, I would call MyTextBox.AppendText("...")
However, this is not very MVVM like. If I want to make a call to a function on a control using binding to my ViewModel, what is an elegant way to achieve this?
I'm using MVVM Light.
Update
I would use the answer from #XAML Lover if I wanted a simple, quick solution. This answer uses a Blend Behavior which is less C# coding.
I would use the answer from #Chris Eelmaa if I wanted write a reusable Dependency Property which I could apply to any TextBox in the future. This example is based on a Dependency Property which, while slightly more complex, is very powerful and reusable once it is written. As it plugs into the native type, there is also slightly less XAML to use it.
Basically when you call a method from a control, it is obvious that you are doing some UI related logic. And that should not sit in ViewModel. But in some exceptional case, I would suggest to create a behavior. Create a Behavior and define a DependencyProperty of type Action<string> since AppendText should take string as a parameter.
public class AppendTextBehavior : Behavior<TextBlock>
{
public Action<string> AppendTextAction
{
get { return (Action<string>)GetValue(AppendTextActionProperty); }
set { SetValue(AppendTextActionProperty, value); }
}
// Using a DependencyProperty as the backing store for AppendTextAction. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AppendTextActionProperty =
DependencyProperty.Register("AppendTextAction", typeof(Action<string>), typeof(AppendTextBehavior), new PropertyMetadata(null));
protected override void OnAttached()
{
SetCurrentValue(AppendTextActionProperty, (Action<string>)AssociatedObject.AppendText);
base.OnAttached();
}
}
In the OnAttached method, I have assigned the extension method that I have created on TextBlock to the DP of Behavior. Now we can attach this behavior to a TextBlock in View.
<TextBlock Text="Original String"
VerticalAlignment="Top">
<i:Interaction.Behaviors>
<wpfApplication1:AppendTextBehavior AppendTextAction="{Binding AppendTextAction, Mode=OneWayToSource}" />
</i:Interaction.Behaviors>
</TextBlock>
Consider we have a property in ViewModel with same signature. And that property is the source of this binding. Then we can invoke that Action anytime, which will automatically invoke our extension method on TextBlock. Here I am invoking the method on a button click. Remember in this case, our Behavior acts like an Adapter between View and ViewModel.
public class ViewModel
{
public Action<string> AppendTextAction { get; set; }
public ICommand ClickCommand { get; set; }
public ViewModel()
{
ClickCommand = new DelegateCommand(OnClick);
}
private void OnClick()
{
AppendTextAction.Invoke(" test");
}
}
Seems like a reasonable request to me. AppendText is definitely very fast, as it deals with pointers. Pretty much every answer in MVVM world be either subclassing, or attached properties.
You can create new interface, call it ITextBuffer:
public interface ITextBuffer
{
void Delete();
void Delete(int offset, int length);
void Append(string content);
void Append(string content, int offset);
string GetCurrentValue();
event EventHandler<string> BufferAppendedHandler;
}
internal class MyTextBuffer : ITextBuffer
{
#region Implementation of ITextBuffer
private readonly StringBuilder _buffer = new StringBuilder();
public void Delete()
{
_buffer.Clear();
}
public void Delete(int offset, int length)
{
_buffer.Remove(offset, length);
}
public void Append(string content)
{
_buffer.Append(content);
var #event = BufferAppendedHandler;
if (#event != null)
#event(this, content);
}
public void Append(string content, int offset)
{
if (offset == _buffer.Length)
{
_buffer.Append(content);
}
else
{
_buffer.Insert(offset, content);
}
}
public string GetCurrentValue()
{
return _buffer.ToString();
}
public event EventHandler<string> BufferAppendedHandler;
#endregion
}
This will be used throughout the viewmodels. All you have to do now, is write an attached property that takes advance of such interface, when you do bindings.
Something like this:
public sealed class MvvmTextBox
{
public static readonly DependencyProperty BufferProperty =
DependencyProperty.RegisterAttached(
"Buffer",
typeof (ITextBuffer),
typeof (MvvmTextBox),
new UIPropertyMetadata(null, PropertyChangedCallback)
);
private static void PropertyChangedCallback(
DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs depPropChangedEvArgs)
{
// todo: unrelease old buffer.
var textBox = (TextBox) dependencyObject;
var textBuffer = (ITextBuffer) depPropChangedEvArgs.NewValue;
var detectChanges = true;
textBox.Text = textBuffer.GetCurrentValue();
textBuffer.BufferAppendedHandler += (sender, appendedText) =>
{
detectChanges = false;
textBox.AppendText(appendedText);
detectChanges = true;
};
// todo unrelease event handlers.
textBox.TextChanged += (sender, args) =>
{
if (!detectChanges)
return;
foreach (var change in args.Changes)
{
if (change.AddedLength > 0)
{
var addedContent = textBox.Text.Substring(
change.Offset, change.AddedLength);
textBuffer.Append(addedContent, change.Offset);
}
else
{
textBuffer.Delete(change.Offset, change.RemovedLength);
}
}
Debug.WriteLine(textBuffer.GetCurrentValue());
};
}
public static void SetBuffer(UIElement element, Boolean value)
{
element.SetValue(BufferProperty, value);
}
public static ITextBuffer GetBuffer(UIElement element)
{
return (ITextBuffer)element.GetValue(BufferProperty);
}
}
The idea here is to wrap StringBuilder into an interface (as it raises no events by default :) which can then be exploited by an attached property & TextBox actual implementation.
In your viewmodel, you'd probably want something like this:
public class MyViewModel
{
public ITextBuffer Description { get; set; }
public MyViewModel()
{
Description= new MyTextBuffer();
Description.Append("Just testing out.");
}
}
and in the view:
<TextBox wpfApplication2:MvvmTextBox.Buffer="{Binding Description}" />
Sorry to be cliche... but I'm pretty new to WPF and MVVM so I'm not sure how to handle this properly. I have a WinForms control within one of my views that I need to modify in it's code behind when an event is raised in the ViewModel. My view's datacontext is inherited so the viewmodel is not defined in the views constructor. How would I go about properly handling this? I am not using any frameworks with built in messengers or aggregators. My relevant code is below. I need to fire the ChangeUrl method from my ViewModel.
EDIT: Based on the suggestion from HighCore, I have updated my code. I am still not able to execute the ChangeUrl method however, the event is being raised in my ViewModel. What modifications need to be made??
UserControl.xaml
<UserControl ...>
<WindowsFormsHost>
<vlc:AxVLCPlugin2 x:Name="VlcPlayerObject" />
</WindowsFormsHost>
</UserControl>
UserControl.cs
public partial class VlcPlayer : UserControl
{
public VlcPlayer()
{
InitializeComponent();
}
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set
{
ChangeVlcUrl(value);
SetValue(VlcUrlProperty, value);
}
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null));
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}
}
view.xaml
<wuc:VlcPlayer VlcUrl="{Binding Path=ScreenVlcUrl}" />
ViewModel
private string screenVlcUrl;
public string ScreenVlcUrl
{
get { return screenVlcUrl; }
set
{
screenVlcUrl = value;
RaisePropertyChangedEvent("ScreenVlcUrl");
}
}
WPF does not execute your property setter when you Bind the property, instead you must define a Callback method in the DependencyProperty declaration:
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set { SetValue(VlcUrlProperty, value); }
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null, OnVlcUrlChanged));
private static void OnVlcUrlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var player = obj as VlcPlayer;
if (obj == null)
return;
obj.ChangeVlcUrl(e.NewValue);
}
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}