WPF MVVM Bind User Control to Main Window View Model - wpf

I asked a similar question a while back here WPF MVVM User Control. I got some answers, but they were way off, so I guess I wasn't clear in what I want to do....
I am working on a WPF application using MVVM. The app is built using a component based approach, so I have some user controls that I've defined that will be used throughout the application. As an example, I have an Address control. I want to use it an multiple places throughout the application. Here's an example. See here:
http://sdrv.ms/1aD775H
The part with the green border is the Address control. The control has its own View Model.
When I place it on a window or other control, I need to tell it the PK of the customer to load addresses for. So I created a Customer ID DependencyProperty:
public partial class AddressView : UserControl
{
public AddressView()
{
InitializeComponent();
}
public static DependencyProperty CustomerIdProperty = DependencyProperty.Register("CustomerId", typeof(int), typeof(AddressView),
new UIPropertyMetadata(0, AddressView.CustomerIdPropertyChangedCallback, AddressView.CustomerIdCoerce, true));
public int CustomerId
{
// THESE NEVER FIRE
get { return (int)GetValue(CustomerIdProperty); }
set { SetValue(CustomerIdProperty, value); }
}
private static void CustomerIdPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
// THIS NEVER FIRES
AddressView instance = (AddressView)d;
instance.CustomerId = (int)args.NewValue;
}
enter code here
private static object CustomerIdCoerce(DependencyObject d, object value)
{
return value; // <=== THIS FIRES ON STARTUP
}
}
Then in the MainWindowView I have:
<vw:AddressView Grid.Row="1"
Grid.Column="0"
x:Name="AddressList"
CustomerId="{Binding ElementName=TheMainWindow, Path=SelectedCustomer.Id, Mode=TwoWay}"/>
Note my comments in the user control's CS. The Coerce fires on startup. The callback never fires, nor do the CustomerId getter or setter.
What I would like to happen seems simple, I just can't make it work....
When a customer is selected the CustomerId should be passed to the Address UserControl. Then in the VM for the Address UserControl should handle getting & saving the data.
So, again, 2 questions:
1) Anyone see what's wrong?
2) How does the UserControl DP send the PK to the ViewModel?
If anyone's interested, my sample project is here: http://sdrv.ms/136bj91
Thanks

Your CustomerId getter and setter will never fire in this situation. They are there simply as helper methods in case you want to access the CustomerIdProperty property from your code behind.
Your CustomerIdPropertyChangedCallback method will not fire because your binding expression is incorrect. You need to bind to the DataContext of MainWindow and not the window itself:
...
CustomerId="{Binding ElementName=TheMainWindow, Path=DataContext.SelectedCustomer.Id}"
...
Also, make sure that you are calling the INotifyPropertyChanged PropertyChanged event when the property bound to the ComboBox is changed.

Try this :
CustomerId="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.YourSelectedItem.TheProperty}"
I am not sure how to manage your seleted item in window, so please change yourSelectedItem.TheProperty accordingly.

Related

Click on CheckBox Binding

I have Car.xaml:
<CheckBox Name="carA" IsChecked="{Binding CarACheck, Mode=TwoWay, FallbackValue=true}" />
Then in Car.xaml.cs:
public Car()
{
//...
DataContext = this;
}
public bool CarACheck
{
get => carA.IsChecked;
set => carA.IsChecked = value;
}
When I run it and click on CheckBox, the app crashes with the error below:
set => carA.IsChecked = value;
System.StackOverflowException
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
Cause of Error
The reason is that your setter is called over and over.
The CheckBox is clicked.
The IsChecked property of the CheckBox is set.
The binding sets value on the bound property CarACheck.
The setter of CarACheck sets the IsChecked property of the CheckBox.
Go to 3.
In the long run this causes the stack to overflow and the application crashes.
An MVVM Solution
It seems that you are trying to build a view that displays data about cars. What I see from your sample is that you mix your data and most likely business logic with the user interface code. You should not do that, because it harms code quality and maintainability in the long run. A better and sustainable approach is to separate your view from data and business logic. This is what the MVVM pattern is about.
In your sample code, we have a view, which we would call CarView. This is where the CheckBox is defined along with the rest of the user interface to represent a car. The data is exposed to the view through a separate view model type called CarViewModel. This view model contains properties that are bound from the view.
When using simple properties, regardless of having a backing field or not, the bindings are not able to determine changes to properties from the view model side. This is why you have to implement the INotifyPropertyChanged interface, which provides an event for this purpose. The event is raised, whenever a property is changed. Usually this is in done in the setter.
public class CarViewModel : INotifyPropertyChanged
{
private bool _carACheck;
public bool CarACheck
{
get => _carACheck;
set
{
if (_carACheck == value)
return;
_carACheck = value;
OnPropertyChanged(nameof(_carACheck));
}
}
// ...other code, maybe even setting CarACheck.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In the view, simply assign an instance of this view model to the DataContext in XAML or code-behind, e.g.:
public Car()
{
// ...other code.
DataContext = new CarViewModel();
}
In your view, you do not need a name for the CheckBox, as there is no explicit reference to it. The Mode=TwoWay declaration can be removed, too, because the IsChecked property binds two-way by default.
<CheckBox IsChecked="{Binding CarACheck, FallbackValue=true}"/>
Remarks about your original code: I hope that you can see the benefits of the MVVM pattern. Of course, after reviewing this solution, you could simply add a backing field and INotifyPropertyChanged to your current code to make it work, too, but the lesson to learn here is that this separation is valuable and worth investing although it might seem more verbose. See also:
Data binding overview (WPF .NET)
How to: Implement Property Change Notification

c# wpf Binding Command - access value of textbox from command

I want to call a simple Command which adds values from my GUI into the database.
My Command:
private ICommand addSpeechCommand;
public ICommand AddSpeechCommand
{
get
{
if (addSpeechCommand == null)
{
addSpeechCommand = new RelayCommand(param => AddSpeech());
}
return addSpeechCommand;
}
}
public void AddSpeech()
{
// TODO add
OrganizerBL.InsertSpeech(new Speech(maxSpeechId, currentSpeech.Title, currentSpeech.Summary, currentSpeech.Start, currentSpeech.End, currentSpeech.TrackId, currentSpeech.SpeakerId, currentSpeech.ConferenceId, currentSpeech.Room));
this.LoadSpeeches();
}
-- this commented out row shows how i dealt with it when a row of my datagrid was selected. but i want it to work without a currentSpeech
My XAML:
<Label x:Name ="lblTitle" Content="Title"/>
<TextBox x:Name="txtTitle" Text="{Binding CurrentSpeech.Title, Mode=TwoWay}" Margin="2,144,0,0" Height="20" VerticalAlignment="Top"/>
and other textboxes...
I really don't know how to access the values of the textboxes from the command to call my insertSpeech method...
Sorry for my english :)
UPDATE:
I'm getting a nullreference exception because my currentSpeech is null.
Is there a way to solve this without the currentSpeech?
The reason you get the NullReferenceException is probably because it's instanced in the property itself. When you cerate a binding, it's created to the property as it is at that stage. And you bind to the property when it's NULL. IT's actually created inside the property, but the Binding will never know that.
First of all, I would remove all logic from properties.
I would also implement the INotifyPropertyChanged to the class, and call the PropertyChanged in the property's "set". This means that the UI will know of any changes to the porperty.
Then I would create a depencency property for the property, if it's used in any Binding ot XAML.
Last, I would instance the command in the class's constructor.
Logic don't (in my book) belong to properties.
How to do
1. Bind TextBox.Text to View model property
2. Use View model property in Command Handler.
In your case, You have binded TextBox.Text to CurrentSpeech.Title, but using this.Title.
In you command, change this.Title to currentSpeech.Title

Getting Value from ViewModel through DataContext WITHOUT Binding?

New to WPF. I am creating UserControls that need read access to the ViewModel state to do their thing. I currently use the following technique:
public partial class ControlBar : UserControl
{
private static readonly DependencyProperty URLProperty =
DependencyProperty.Register("URL", typeof(string), typeof(ControlBar),
new UIPropertyMetadata(null));
public ControlBar()
{
InitializeComponent();
SetBinding(URLProperty, "CurrentPage.URL");
Pin.Click += Pin_Click;
}
private void Pin_Click(object sender, RoutedEventArgs e)
{
var URL = (string)GetValue(URLProperty);
}
}
Is this the correct way and is it not overkill to set up a long-term binding for each variable I need access to? Or can you do something like:
GetValue(new Path("CurrentPage.URL.....
I made up the above obviously.
Thanks!
In general data-binding is the way to go. However sometimes when you are creating controls that have view-specific concerns for which data-binding will not be appropriate.
In those cases you will want to be able to interact with the DependencyProperty to set it and know when it changes. I have been following a pattern that I picked up from a Charles Petzold article in MSDN magazine.
My answer to another question shows the pattern for creating a DependencyProperty for a UserControl Stack Overflow: Dependency Property In WPF/SilverLight
Again, data-binding to a view model will likely solve your problem, but a DependencyProperty may come in useful depending on the situation.
Update in response to comment:
In many situations you can data bind your in a UserControl without using a DependencyProperty. For example if you have a TextBlock that displays a name you would put a TextBlock in the XAML of the UserControl
<TextBlock Text="{Binding Path=NameString}" />
In the view model which is present in the DataContext you would have a property NameString and if the TextBlock is to update the display when the NameString property changes the view model should implement INotifyPropertyChanged and the property should fire the PropertyChanged event with the name of the property sent along with the event.
protected string _NameString;
public string NameString
{
get { return _NameString; }
set { _NameString = value: Notify("NameString"); }
}
Where Notify is a method that checks the PropertyChanged event for null and sends the event if not null.
This works well if everywhere that you want to use the UserControl has a view model with a Name property. The great thing is that the UserControl can pick up on the DataContext of wherever it is hosted and bind to an external view model.
When you want to start binding the same UserControl to different properties is one place that you may want to use a DependencyProperty. In that case you could make a UserControl with a DependencyProperty and bind it to different properties
<my:SampleControl NameString="{Binding Path=GivenName}" />
<my:SampleControl NameString="{Binding Path=FamilyName}" />
And then have an internal view model that the DependencyProperty change handler updates when the bound property changes.
Update: No DependencyProperty or binding
You can always add an ordinary C# property to the UserControl and pass the data in that way.
public MyClass Data { get; set; }
Then in the code-behind of the UserControl you can simply use the property:
if (this.Data != null)
{
this.textBox1.Text = Data.NameString;
}
Update in response to comment:
Another way to access the view model in code is to cast the DataContext to your view model type:
MyClass data = this.DataContext as MyClass;
if (data != null)
{
// do something
this.textBox1.Text = data.NameString;
}

Recommend best approach in this situation

I'm experimenting with MVVM and I can't quite wrap my mind around it.
I have a View (windows) that has several repeated controls.
Let's say I have 4 textbox - button pairs. Their behavior should be the same, after pressing a button paired textbox says "Hello World!"
I have tried several options I could think of:
One ViewModel, 4 string properties binded to textboxes, 1 command
When I bind each button to the same command I can't tell which property needs to be set.
Passing enum to CommandParameter feels awkward.
One ViewModel and UserControl that hosts a textbox and a button repeated 4 times.
Now I need to expose all the properties like Command, CommandParameter, Text etc.. Seems like a lot of work.
Still can't say which property needs to be updated after click.
Each UserControl has a ViewModel
This solves button clicking and setting property, but now I have no clue how to extract texts from nested ViewModels to the window ViewModel.
Is there any other way? I suspect DataTemplates could be of use, but I'm not sure how.
What you describe is such an abstract and contrived idea that it doesn't warrant MVVM. You're talking about TextBoxes and Buttons, which are all 'View', and not the MVVM way of thinking. You'd almost always start with a Model.
There is no 'Model' per-se here though; your specification is literally to set the value of a TextBox on a Button click. The seemingly random list of '4' items (picked out of nowhere) and a seemingly useless TextBox mean nothing.
Putting that aside and assuming that you have a set of 4 business entities, each with a field on them that is user-editable, and an action that the user can trigger, you'd do this:
Create a ViewModel class to represent an item - eg MyItemModel
Create a ViewModel class to represent the set of items (likely it will just return a Collection of the first) - eg AllMyItemsListModel
Then for the View:
Create an ItemsControl, with ItemsSource bound to an instance of the 'collection' of the second ViewModel class
For each ItemTemplate, have a template or UserControl for each item
Within the template or UserControl, bind the TextBox's Text property to the appropriate property of the first class
Bind the Button's Command property to a property on the first class returning an ICommand - using RelayCommand for example
I don't know what you mean about 'extracting texts from nested ViewModels to the window ViewModel' - what does that mean and why would you want to do it?
Hope that helps.
Number 3. Except there would just be one UserControl with viewmodel and then the Main page that would have multiple instances of that UserControl on it. If the main window needs info from the UserControl you could pass it through events or use something like MVVM Light and it's messenger class.
There's no need to create a separate ViewModel for a reusable control that has such simple behavior. Just by adding a few DependencyProperties and an event handler to the simple UserControl you can reuse the logic and only set the properties that are actually different on each instance. For the UserControl XAML you just need to hook up the TextBox to the DependencyProperty and the Button to a Click handler.
<DockPanel>
<Button Content="Reset" Click="Button_Click"/>
<TextBox Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=Text}"/>
</DockPanel>
The code for the UserControl just needs to define the properties that can be bound externally and the handler to reset the Text.
public partial class ResetTextBox : UserControl
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(ResetTextBox),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty ResetTextProperty = DependencyProperty.Register(
"ResetText",
typeof(string),
typeof(ResetTextBox),
new UIPropertyMetadata(String.Empty));
public string ResetText
{
get { return (string)GetValue(ResetTextProperty); }
set { SetValue(ResetTextProperty, value); }
}
public ResetTextBox()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Text = ResetText;
}
}
Then to use the control you just need to bind to your ViewModel string properties and set the default text that should be applied on a reset which can either be hardcoded here, or bound to other VM properties.
<StackPanel>
<local:ResetTextBox ResetText="One" Text="{Binding Name1}"/>
<local:ResetTextBox ResetText="Two" Text="{Binding Name2}"/>
<local:ResetTextBox ResetText="Three" Text="{Binding Name3}"/>
</StackPanel>

WPF - CanExecute doesn't fire when raising Commands from a UserControl

I've got a button strip usercontrol that I want to have on most of my forms.
I've added the commands as follows ...
public ICommand Create
{
get
{
return buttonCreate.Command;
}
set
{
buttonCreate.Command = value;
}
}
I've set these as dependency properties so I can bind to them ...
public static readonly DependencyProperty CreateCommandProperty =
DependencyProperty.Register(
"Create",
typeof(ICommand),
typeof(StandardButtonStrip),
new PropertyMetadata((ICommand)null));
I'm then binding my usercontrol to a command ...
<commoncontrols:StandardButtonStrip HorizontalAlignment="Stretch" Create="{Binding CreateCommand}" />
I'm setting up the command as follows ...
_viewModel.CreateCommand = new DelegateCommand<object>(OnCreateCommand, CanCreate);
but despite the fact that i'm always returning true on my CanCreate method the button is disabled ... if I put a break point on return true it never fires!
public bool CanCreate(object parm)
{
return true;
}
I've tried this to see if it will refresh the binding, but no joy!
_viewModel.CreateCommand.RaiseCanExecuteChanged();
I think the problem is down to the user control and how I'm passing the Command up as a property, but not sure ...
The way this looks, you have a dependency property on a view model. If you are really using MVVM, that is definetly not the way to go about it (not because of religious adherence to a pattern, but because it's not the optimal way).
First of all, is your view model a DependencyObject?
If it is, you should downgrade it to a class which implements INotifyPropertyChanged. Why? Because the Button's Command property is a DependencyProperty itself (inherited from ButtonBase) and supports databinding already.
If it isn't, then a dependency property on it will not work, which is fine, because you shouldn't have dependency properties on your view model in the first place.
What you should do, is have the view model as the DataContext for your control (I'm guessing you already have that set up). Then change your view model's CreateCommand to a plain ICommand, and bind the createButton's Command property like this (in your default StandardButtonStrip style)
<Button Name="createButton" HorizontalAlignment="Stretch" Command="{Binding CreateCommand}" />
This way, it will still be reusable, you just need to ensure that any view model you associate with your user control has a property CreateCommand of type ICommand (and that view model will be inherited down to the button control by default - one of the nicest things the wpf guys thought up).
So to recap, you should do it the other way around, more or less.
Hope this was helpful, cheers.
One caveat to the accepted answer -
Using a delegate command I could only get this to work if I created a new
Command<T> : DelegateCommand<T> and hooked up the Command Manager.
event EventHandler ICommand.CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
Have you overridden any of the user control's functionality?
One common problem is overriding a method without calling the base's implementation. For example, if you've overridden OnContentChanged() without calling base.OnContentChanged(), you may have accidentally supressed the firing of the ContentChanged event.

Resources