I'm trying to data bind a Slider control to a custom view model, and then bind the same property from the view model to a custom type that inherits from DependencyObject. The binding mode between the view model and the Slider control is two way, but the mode between the view model and the custom DependencyObject type should only be one way (the type should not be able to change the view model value).
Here's the relevant bit from my view model:
public class ScanViewModel : DependencyObject
{
public static readonly DependencyProperty CurrentScanIndexProperty = DependencyProperty.Register("CurrentScanIndex", typeof(Int32), typeof(ScanViewModel), new PropertyMetadata(0));
public Int32 CurrentScanIndex
{
get { return (Int32)GetValue(CurrentScanIndexProperty); }
set { SetValue(CurrentScanIndexProperty, value); }
In my XAML I bind the slider control as follows:
<Slider x:Name="scanIndexSlider" Minimum=0 Maximum = 100 Value="{Binding CurrentScanIndex, Mode=TwoWay, Delay=5}"
I have a 3rd object that participates as well:
public class CustomIndicator : DependencyObject
{
public static readonly DependencyProperty ScanIndexProperty = DependencyProperty.Register("ScanIndex", typeof(Int32), typeof(CustomIndicator), new PropertyMetadata(0));
public Int32 ScanIndex
{
get { return (Int32)GetValue(ScanIndexProperty); }
set { SetValue(ScanIndexProperty, value); }
}
public CustomIndicator(ScanViewModel ViewModel)
{
// Data bind to the view model programmatically:
Binding binding = new Binding("CurrentScanIndex");
binding.source = ViewModel;
binding.Mode = BindingMode.OneWay;
BindingOperations.SetBinding(this, CustomIndicator.ScanIndexProperty, binding);
}
I assign an instance of "ScanViewModel" as the DataContext to the view containing the slider and the binding works i.e. I manipulate the slider and the dependency property on the view model changes to reflect the new slider value. However, the view models' new value is not then passed on to the "CustomIndicator.ScanIndex" dependency property that it was bound to during the CustomIndicator constructor method. If I run through step by step I can see the binding in the constructor seems to work initially...after the programmatic bindg in customIndicator contructor is executed the objects' "ScanIndex" reflects the same value as the view model to which it was just bound, so the binding works initially. However, "ScanIndex" on "CustomIndicator" never changes after that initial change. It's as if the binding works once (in the constructor) and then never again after that. As I mentioned, the binding between the Slider control and the view model works fine.
I should add that after instantiation the "CustomIndicator" object is then added to the Children collection of a custom UserControl that has its own DataContext (a different type). Could this be the problem?
Related
I am using a bindable property in a custom control in order to set a property from the xaml code. However, it seems like my property always will get the default value that I've specified for the bindable property.
My xaml code:
<controls:MyView ID="4" />
My code behind:
public partial class MyView : ContentView
{
public static readonly BindableProperty IDProperty = BindableProperty.Create(
nameof(ID),
typeof(int),
typeof(MyView),
15);
public int ID
{
get
{
return (int)GetValue(IDProperty);
}
set
{
SetValue(IDProperty, value);
}
}
private MyViewViewModel viewModel;
public MyView()
{
InitializeComponent();
viewModel = new MyViewViewModel() {};
BindingContext = viewModel;
}
}
I expect that my property should get value 4 in this example, but it always get the default value 15. Should the property be set in the constructor or later?
What am I doing wrong?
Why do you embed a ViewModel inside your custom control? It is weird and even wrong. The idea behind a custom control is that you could reuse and bind it to the parent's ViewModel. Think of a simple Button control, it is reusable by simple placing it on the screen and setting the BindableProperties like Text, Command and etc. It is working because it's BindingContext by default is the same as it's parent.
In your case you sort of isolate your control from any modifications, since you set the BindingContext to a private custom ViewModel class. You have to rethink your solution.
It should be as simple as:
public partial class MyView : ContentView
{
public static readonly BindableProperty IDProperty = BindableProperty.Create(
nameof(ID),
typeof(int),
typeof(MyView),
15);
public int ID
{
get => (int)GetValue(IDProperty);
set => SetValue(IDProperty, value);
}
public MyView()
{
InitializeComponent();
}
}
I see it's too late, but i have been suffering for a while now, that - for a reason that i don't know - a ContentView(Custom view element) won't bind when you set it's BindingContext property in any way other than this:
<ContentView x:Class="mynamespace.CustomViews.MyView"
....
x:Name="this">
then on the main container element (in my case a frame) set the BindingContext
<Frame BindingContext="{x:Reference this}"
....>
Setting the BindingContext in the constructor - in MyView.xaml.cs - does not work, while this way - and other ways - work in binding a View to another class (a view model), it does not - i repeat - work in binding ContentView to its code_behind.cs file.
In you xaml , do
<controls:MyView ID="{Binding Id}" />
And then in ViewModel, Create a porperty called Id
public int Id {get; set;} = 4;
You don't need Bindable property if your are not binding , Just Create a Normal Property of type int With ID as property name.And then you can assign the ID from XAML.(Intellisense will also show the ID property)
public int ID
{
get;set;
}
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 userControl named SensorControl which I want to bind its dataContext to a viewModel class named SensorModel.
The binding is successful and the initial values of viewModel is visible in the userControl, but the problem is that when I change the viewModel's properties, the userControl does not update, and I have to manually update that (by assigning null to its dataContext and assigning the viewModel again).
Here is a simplified example of my code.
SensorControl.xml:
<UserControl ...[other attributes]... DataContext={Binding Model}>
.
.
.
<Label Content="{Binding Title}"/>
.
.
.
</UserControl>
SensorControl.xml.cs (code-behind):
public partial class SensorControl : UserControl
{
public SensorModel model;
public SensorModel Model
{
get { return model; }
set { model = value; }
}
public SensorControl(SensorModel sm)
{
Model = sm;
}
}
MainWindow.xml.cs:
public partial class MainWindow : Window
{
public SensorModel sensorModel_1;
public SensorControl sensorControl_1;
public MainWindow()
{
InitializeComponent();
sensorModel_1 = new SensorModel(...[some arguments]...);
sensorControl_1 = new SensorControl(sensorModel_1);
mainGrid.Children.Add(sensorControl_1);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
sensorModel_1.Title = "myTitle";
//The UserControl does not update
}
0) I implemented INotifyPropertyChanged in SensorModel
1) The reason I need this, is that there is only one single concept of 'Sensor' in my project (it is a real electronic sensor) and therefore I have a single model for it (that deal with the real sensor, the database, etc), but in the UI I have multiple userControls for presenting different aspects of Sensor. So I have to create one instance of model (SensorModel) for each real sensor, and multiple userControls must bind to that (each one uses different parts of model).
2) I'm not that new to WPF, but I'm kind of new to MVVM and it's possible that I misunderstand something essential, so I would appreciate if someone could clearly explain the correct approach.
Thanks in advance
In your UserControl, remove the DataContext attribute and add an x:Name attribute. Then in your Label, bind like this:
<UserControl x:Name="uc">
<Label Content="{Binding ElementName=uc,Path=Model.Title}" />
</UserControl>
I believe the issue is the DataContext can't be set to Model because binding works off the parent's DataContext which will be based on mainGrid when it gets added as a child to that. Since the property "Model" doesn't exist in maiGrid's DataContext no binding will occur so your update won't reflect. Getting the DataContext of a UserControl properly can be tricky. I use the ElementName quite a bit or create DependencyProperties on the UserControl and then set them from the parent who will be using the control.
You need to set the DataContext to your ViewModel class in your View, and if you're applying the MVVM pattern, you should use ICommand for actions. Maybe it would be better If you'd implement a MainView class that does the logic in the background instead in the MainWindow class.
public MainWindow()
{
InitializeComponent();
DataContext = new MainView();
// sensorModel_1 = new SensorModel(...[some arguments]...);
// sensorControl_1 = new SensorControl(sensorModel_1);
// mainGrid.Children.Add(sensorControl_1);
}
Your MainView class :
public class MainView {
public SensorControl Control {get; internal set;}
...
}
And in your xaml change the binding :
<Label Content="{Binding Control.Model.Title}"/>
Thanks to all of you guys, I took your advices and finally I found a way.
1) I implemented an event in the SensorModel that fires every time any of properties changes (name ModelChanged)
2) Then as Merve & manOvision both suggested, I declared a dependency property in the SensorControl (of type SensorModel) and bind ui elements to that (Binding Path=Model.Title)
3) Then I used the ModelChanged event of this dependency property in the SensorControl and raise an event (of type INotifyPropertyChanged) so the bindings update their value
It works fine.
Without breaking MVVM, is there a way to expose some properties of a child control in a user control so that the window or other user control that utilizes it can access these properties directly?
For instance I have a user control that has a listview set up with gridviewcolumns, headers, and is bound to a view model. But the list view in the user control has selected item properties and such that I'd like to expose to the host without having to do something like usercontrol.customListView.property. Or is that how I should do it? I'd like to go just usercontrol.property, omitting customListView. Perhap I should just create properties in the user controls code behind that return the list view controls properties that I want attached directly to the user control?
I feel like that latter option doesn't really break MVVM since they are exposed for the host to interact with, not really related to the view itself. Any suggested would be appreciated.
EDIT: In fact, I'd really like to have a SelectedItem property directly on the user control that is not ListViewItem or object, but actually of the datatype contained that doe like:
public MyDataType SelectedItem {
get {
return customListView.SelectedItem as MyDataType;
}
}
Would that be permissible in MVVM? Because I don't see how I could have that in the ViewModel, seems like it would have to be in the partial class code behind.
This is pretty common task when you want to put something repeated into UserControl. The simplest approach to do so is when you are not creating specialized ViewModel for that UserControl, but sort of making custom control (build with the use of UserControl for simplicity). End result may looks like this
<UserControl x:Class="SomeNamespace.SomeUserControl" ...>
...
<TextBlock Text="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}" ...>
</UserControl>
.
public partial class SomeUserControl : UserControl
{
// simple dependency property to bind to
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(SomeUserControl), new PropertyMetadata());
// has some complicated logic
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(SomeUserControl),
new PropertyMetadata((d, a) => ((SomeUserControl)d).ValueChanged()));
private void ValueChanged()
{
... // do something complicated here
// e.g. create complicated dynamic animation
}
...
}
Usage will looks like this in containing window
<l:SomeUserControl Text="Text" Value="{Binding SomeValue}" ... />
As you can see SomeValue is bound to Value and there is no MVVM violations.
Of course, you can create a proper ViewModel if view logic is complicated or required too much bindings and it's rather easier to allow ViewModels to communicate directly (via properties/methods).
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.