Using commands inside control templates within resource dictionaries- good or bad? - wpf

I have defined a control template inside resource dictionary.
<Button Command="{Binding Path=DataContext.PrintCommand,
RelativeSource={RelativeSource
Mode=FindAncestor,AncestorType={x:Type UserControl}}}">
I am using relative source to get the data context and access the viewmodel.
In PrintViewModel
public ICommand PrintCommand { get; set; }
In Usercontrol, I set datacontext=PrintViewModel
Its working for me. But is it bad practice?
Since this can be consumed by user controls only due to the use of relative source ,making it conditional that a usercontrol is up the tree and defines a particular command?
would like to know some thoughts.

I can't say whether or not it is bad practice, but if I were you i would try a different approach.
If your Button is situated within the UserControl they should share DataContext. If they aren't, you could simply define a DataContext for the Button, not really the best choice either.
A second option would be to bind by using an Elementname and the Path.
Like this:
<Button Command="{Binding ElementName="UserControlsName", Path="PrintCommand}">
This requires you to give your UserControl a name e.g: <UserControl x:Name="MyUserControl"/>
and it should be in the same XAML-file.

Related

Call controls inside view(xaml file) in viewmodel

I want to call controls inside view like button and item template inside viewmodel. Please tell how can I do that. My view contains following
<ItemsControl Name="cDetails"
Width="395"
ItemTemplate="{DynamicResource Test}"
ItemsSource="{Binding ViewModels}"
Visibility="{Binding IsLoaded,
Converter={StaticResource visibilityConverter}}">
<Button Name="btnComplete"
Grid.Column="1"
HorizontalAlignment="Center"
Command="{Binding AuditCommand}"
CommandParameter="1">
Complete
</Button>
Please tell how can I call these items in my viewmodel using vb.net.
Thanks
Accessing your view components from inside your viewmodel is not the way to do things in MVVM. Because it is specifically not designed to work this way, you will have to go out of your way to make it work. You should probably investigate how to accomplish your goals using MVVM properly, or forego using MVVM at all and do the work in your code-behind.
Since you have not described what your goal is, it is hard to provide specific recommendations. In general when using MVVM, you manipulate things in your viewmodel and set properties. Your view binds to these properties so that it updates appropriately as they are being set. Your viewmodel does not directly manipulate the views themselves, only the viewmodel properties that they are bound to.
For example, let's say you are updating the text on a TextBlock. You could do something like this in xaml:
<TextBlock Text="{Binding SomeText}" />
Then, your viewmodel (which should implement the INotifyPropertyChanged interface) defines this property and sets it as desired.
public string SomeText
{
get { return _someText; }
set
{
if (_someText != value)
{
_someText = value;
NotifyPropertyChanged("SomeText");
}
}
}
private string _someText;
...
// At any time, you can set the property, and the
// binding will update the text in the control for you.
SomeText = "Some text";
If you absolutely need to manipulate your views from code (or if you are not using MVVM), the appropriate place for that sort of code is the "xaml.cs" file next to your view (the code-behind). You can assign a name to anything in your xaml using syntax like <TextBlock x:Name="SomeTextBlock" /> and then access it from the code-behind as a member variable with the same name. For example, you could do SomeTextBlock.Text = "Some text". However, this is not usually necessary for the vast majority of use cases if you are using MVVM.
You shouldn't try to access controls directly from the ViewModel. The ViewModel must not know about the View implementation.
Instead, in WPF we connect View and ViewModel through Bindings. Bindings connect Properties of controls in the View, with Properties in the ViewModel.
Commands are a special type of Property that can be bound as actions for controls like Button.
In your example, you would need to have these properties in your ViewModel:
A collection named ViewModels
A boolean named IsLoaded
And an ICommand named AuditCommand
By managing those properties, you should be able to control what's shown in your View and its behavior.
If you need more control, create more Bindings to other properties, or create some events in your ViewModel and manage them from your View's code-behind.

Can I inherit a Command class from ICommand rather than have multiple instances of ICommand on one class?

It seems much neater to me to implement a command in a class that represents that command, and derives from ICommand. This way I can also export and import commands in an MEF composite application.
Is this possible, or feasible? Is there a known pattern for accomplishing this?
I'm not sure how well this answers your question, since I don't know much about MEF.
There is no need to have direct references to command instances on your class, but you do need some way of presenting a binding source that returns one to a view. You can build a menu by binding a MenuItem's ItemsSource to a property that returns a dynamically generated IEnumerable of commands, for example.
You could also use a value converter to get a command on-demand for some arbitrary identifier:
<Button Command="{Binding Path=CommandId, Converter={StaticResource CommandConverter}}" />
Or pass the identifier directly from XAML through Binding.Source:
<Button Command="{Binding Source=DesiredCommand, Converter={StaticResource CommandConverter}}" />
In this case the string "DesiredCommand" would be passed to the converter.

Where should the crud logic be implemented in mvvm?

In my MVVM Light application I do a search in a customer list. The search narrows the customer list which are displayed in a master/detail view with a datagrid (the master CustomerSearchResultView) and a separately defined usercontrol with FirstName, Lastname, Address etc, etc (the detail - CustomerSearchDetailView). Here are the main content of the master/detail view:
<StackPanel MinWidth="150" >
<TextBlock Text="Customer Search Result List" />
<Grid>
<DataGrid Name="CustomerList" ItemsSource="{Binding SearchResult}" SelectedItem="{Binding SelectedRow, Mode=TwoWay}" >
.....
</DataGrid>
</Grid>
<Grid Grid.Column="2">
<TextBlock Text="Customer Details" Style="{StaticResource Heading2}" Margin="30,-23,0,0"/>
<content:CustomerSearchDetail DataContext="{Binding SelectedRow}" />
</Grid>
</Grid>
</StackPanel>
Both have their corresponding ViewModels. Please remark the DC for the CustomerSearchDetail, SelectedRow - it is a property on the CustomerSearchResultViewModel and is defined like this:
private Customer _selectedRow;
...
public Customer SelectedRow
{
get { return _selectedRow; }
set
{
_selectedRow = value;
RaisePropertyChanged("SelectedRow");
}
}
...
Because of this I have not defined any DC on the CustomerSearchDetailView - it is set in the Binding on the "Master" view (as shown above) and it seems to work ok.
In my Model folder I have created the Customer class that is in use here. It implements ObservableObject and IDataErrorInfo and have public properties that raisepropertychanged events.
I run the application and everything seems to be ok. Note: the ViewModel for the CustomerSearchDetailView (that is CustomerSearchDetailViewModel.cs) is at this stage just an empty shell and not in use (as far as I can see ... the constructor is never accessed)
Now I want to add Save/Update functionality to my customer in the detail view. Ok, I add a Save button to the CustomerSearchDetailView like this:
<Button Content="Save" Command="{Binding Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
I create my "SaveCommand" RelayCommand property in my CustomerSearchDetailViewModel - but it is never accessed.
Hmmmmm ... well after some googling back and forth I come up with this:
<Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
I defined the "MyCustDetails" as a resource in this view pointing to the CustomerSearchDetailViewModel. And voila! I now hit the method when debugging ... but alas, my customer was of course "null". (In fact I spent 2 hours implementing the CommandParameter here and binding it to the "SelectedRow" Property on the master view - but the customer was still "null").
More googling and searching for mvvm examples, and I implemented my "SaveCommand" on the Customer class (the model object). And guess what? The edited customer got passed along - I could send it to my EF layer and everything seems to be ok ....
And - If you are still with me - here comes my questions:
1.) I would like - and thought that was the "proper MVVM way" of doing things - to have my CRUD/Repository accessing in the ViewModel. How can I do that in my scenario?
2.) Now that I have my CRUD in place via the Model class (Customer) - should i bother with question 1? In fact I have deleted the CustomerSearchDetailViewModel and everything runs ok. I feel I have invented the View - Model (MV) framework ... :-P
I would very much like feedback on this - and I apologize for this "wall of text".
Assuming DC means DataContext
Just my opinion:
First question is are you doing anything special with SelectedRow in CustomerSearchResultViewModel?
If the answer is no, just get rid of that property and have your CustomSearchDetailView bind directly to the DataGrid using {Binding ElementName=CustomerList, Path=SelectedItem}
Now your Save / update Commands need to be used by Button's in CustomerSearchDetailView. So instantly I'd be inclined to using a separate VM for that View and have these Command's defined there.
Now you mentioned these Commands were not accessed. Well the answer for that is because in your program you're never actually creating the CustomerSearchDetailViewModel.
Normal operation is your View's DataContext is it's VM(If it requires one. In your case you do imo cos you need it to hold your Commands)
looking at your code I'd guess your using MVVM Light. So in ViewModelLocator you have your Main property and in your Main View, you got the DataContext set using that Main property and Source={StaticResource Locator} where Locator is the ViewModelLocator created in App.xaml Resources. This thereby creates that ViewModel for that view defining that DataContext. You can ofcourse do the same in code-behind but let's not go off topic.
So in your case you got the DataContext set as SelectedRow which is of type Customer and Binding's are resolved using DataContext and that's why when your command's are defined in Customer it works fine but when it's in the VM it did not.
So why did it work when you had the commands in your VM and used
<Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
^^ That worked because the DataContext was not used since Source has been specified explicitly. and where-ever MyCustDetails was defined in resources, there the VM got created.
So it worked what's wrong with that?
Well it's quite a big mess. Also just like you mentioned Customer details in that VM was null. Well I hope you can guess why that was by now. It's because your VM was created in resources via x:Key="MyCustDetails" but nothing in it was ever used or set apart from when the Binding's referred to it explicitly
In this system we got commands that refer either to the Model which is plain wrong or the VM which is created as a resource just for this purpose. The DataContext is heavily linked to the "SearchResults" view making it not so easy for future extensions or layout updates.
If we keep the View <-> VM a 1 <-> 1 relattion we can avoid all this confusion. So in summary we can answer both your question's together. While this works, please don't let your code be like this and tweak it to better help expansion for future and comply with some basic guidelines.
So how do we do that?
Approach 1:
In your CustomerSearchDetail View, add a DependencyProperty of type Customer lets call this say SelectedCustomer.
Now replace DataContext="{Binding SelectedRow}" with SelectedCustomer="{Binding SelectedRow}" in CustomerSearchResultView
Now set the DataContext of your CustomerSerachDetailView as it's VM similar to how CustomerSerachResultsView links to it's VM(guessing through DataContext Binding in xaml using the ViewModelLocator)
Now you can have your commands in Button's of CustomerSerachDetailView just as <Button Command="{Binding SaveCommand}" ...
Finally because SelectedRow is no longer the DataContext of the CustomerSerachDetailsView, your Bindings for FirstName, Lastname, Address will all appear to stop working.
We got plenty of options to address this.
First is to in each Binding use a RelativeSource FindAncestor binding pointing to CustomerSerachDetailsView and there via the CurrentCustomer DP(DependencyProperty) we created before get the appropriate field.
eg:
<TextBlock Text={Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomerDetailsView}}, Path=CurrentCustomer.FirstName}" />
now if you have multiple properties this is gonna soon start getting annoying to type. So then pick a common ancestor(say 3 of these TextBlocks are grouped under a StackPanel) and apply it's DataContext as the CurrentCustomer element via a similar binding to ^^. Now the StackPanel's children DataContext will be the Customer element so in each of their binding's you don't have to do the whole RelativeSource thing and can just mention {Binding Path=FirstName} and so on.
That's it. Now you got two view's with their own respective VM and a Model(Customer) and each have their respective tasks.
Great, we done? err not quite yet.
While Approach 1 is better than what we started with it's still just "meh". We could do better.
Approach 2
MVVMLight has a Messenger class that will allow you to communicate between different classes in a weak dependent format. You need to look into this if you haven't already.
So what do we do with Messenger?
pretty simple:
In the setter of SelectedRow in CustomerSearchResultsViewModel we'll send a message with the new incoming value to CustomerSearchDetailsViewModel.
Now in CustomerSearchResultsViewModel we'll add a property CurrentCustomer and assign it this incoming value.
In the CustomerSerachDetailsView we no longer create a DP. Which means we no longer set SelectedRow to anything(DataContext or DP) in the CustomerSerachDetailsView from CustomerSearchResultsView ( sweet less work :) )
As for the way we assign DataContext of CustomerSerachDetailsView or way we bind the Button.Command - They remain same as Approach 1
Finally the actual "FirstName" and so Binding's. Well now CurrentCustomer is a property of the CustomerSearchDetailsViewModel. So binding to it just like how the Button bind's to it's commands
^^ this works fine now cos DataContext for the TextBlock is the VM and the property CurrentCustomer exists in it.

WPF Update Binding when Bound directly to DataContext w/ Converter

Normally when you want a databound control to 'update,' you use the "PropertyChanged" event to signal to the interface that the data has changed behind the scenes.
For instance, you could have a textblock that is bound to the datacontext with a property "DisplayText"
<TextBlock Text="{Binding Path=DisplayText}"/>
From here, if the DataContext raises the PropertyChanged event with PropertyName "DisplayText," then this textblock's text should update (assuming you didn't change the Mode of the binding).
However, I have a more complicated binding that uses many properties off of the datacontext to determine the final look and feel of the control. To accomplish this, I bind directly to the datacontext and use a converter. In this case I am working with an image source.
<Image Source="{Binding Converter={StaticResource ImageConverter}}"/>
As you can see, I use a {Binding} with no path to bind directly to the datacontext, and I use an ImageConverter to select the image I'm looking for. But now I have no way (that I know of) to tell that binding to update. I tried raising the propertychanged event with "." as the propertyname, which did not work.
Is this possible? Do I have to wrap up the converting logic into a property that the binding can attach to, or is there a way to tell the binding to refresh (without explicitly refreshing the binding)?
Any help would be greatly appreciated.
Thanks!
-Adam
The workaround here was to add a property to my object (to be used as the datacontext) called "Self" , which simply returned
public Object Self { get { return this; }}
Then in the binding I used this property:
<Image Source="{Binding Path=Self, Converter={StaticResource ImageConverter}}"/>
Then when I call
PropertyChanged(this, new PropertyChangedEventArgs("Self"))
it works like a charm.
Thanks all.
I don't believe there is a way of accomplishing exactly what you need with your current converter. As you mentioned, you could do the calculation in your ViewModel, or you could change your converter into an IMulitValueConverter.
From your specific scenario (the converter tied to a ViewModel class, and a few of its properties), I would lean towards implementing the logic in the ViewModel.
Hmm, you don't show the full implementation. But I think it should update, if the value bound to the GUI provides the PropertyChanged-Event.
Regards

Databinding to functions in dynamically-loaded plugins

I have several MenuItems whose Commands are bound to my ViewModel. Until today, all of them execute properly.
Now I have added a MenuItem whose ItemsSource is bound to an ObservableCollection. The point of this MenuItem is to enumerate a list of plugins so that the name of all plugins show up. Then when the user clicks on a plugin name, it should call a function to display properties for audio filters.
In my current implementation, which doesn't work, I tried to databind like this:
<MenuItem Header="Filters" ItemsSource="{Binding FilterPluginNames}">
<MenuItem.ItemContainerStyle>
<Style>
<Setter Property="MenuItem.Command" Value="{Binding ShowFilterDialogCommand}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
The problem is that I get a BindingExpression path error because it's trying to use a String as the MenuItem's DataContext.
This leads me to believe that the DataContext for a MenuItem's MenuItems is automatically set to type of objects in the ItemsSource. Is this true?
If I need to change the DataContext, then I'd like to change it to the ViewModel that handles all of my other Commands. But if I do that, how in the world am I able to tell which plugin I want to display filter properties for? I'd need to pass in a CommandParameter at the very least, but binding this value to the filter name isn't my most favorite option. Are there any other ways to do this?
If the DataContext is indeed automatically set to the object type in the ObservableCollection, then I'd rather just call my interface method ShowFilterProperties() directly. I bet that I can't do this without Command binding. If that is the case, how do you all deal with this sort of application? Do you make all of the plugins expose a command handler, which will then show the dialog?
EDIT -- I modified my code to change the ObservableCollection type, and sure enough, WPF wants to databind to the type T. So I guess one option is to have the plugin expose the ICommand, but I don't know if this is a weird approach or not?
EDIT -- ok, I just learned something new. Interfaces can't have fields, so is it not possible to databind with plugins, period?
You are likely not quite binding like you think you are. You might want to just put some diagnostics on your bindings and see what object they are binding to. Here is a good link for debugging bindings:
http://www.beacosta.com/blog/?p=52
Here's a sample:
<Window …
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"
/>
<TextBlock Text="{Binding Path=Caption, diagnostics:PresentationTraceSources.TraceLevel=High}" … />
I think your approach is correct... it probably just needs to be debugged a little.

Resources