Multiple command parameters wpf button object - c#

How can I send multiple parameters from Button in WPF? I am able to send single parameter which is value of TextBox properly. Here is the code.
XAML
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="133,22,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
<Button Content="Button" Grid.Row="1" Height="23" Command="{Binding Path=CommandClick}" CommandParameter="{Binding Text,ElementName=textBox1}" HorizontalAlignment="Left" Margin="133,62,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
Code behind
public ICommand CommandClick { get; set; }
this.CommandClick = new DelegateCommand<object>(AddAccount);
private void AddAccount(object obj)
{
//custom logic
}

Other than using the approach of defining properties in you class (let's call it your ViewModel) to be binded by your view, there are times (not common) where we don't wan't to do so, an important tool to know in these situations is the MultiBinding, so just for completeness sake , even though you are satisfied with the first option, I'll cover another approach.
so to answer your question:
1. MVVM Approach :
Use the MVVM approach and define properties to binded by your view, and use those properties in your ViewModel's command without the need for CommandParameters.
2. MultiBinding : (Can live happily with MVVM approach)
Passing the Command Parameter as a Multi Binded parameter as seen here:
<Button Content="MultiBindingExample" Command="{Binding MyCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MyMultiConverter}">
<Binding Path="..." ElementName="MyTextBox"/>
<Binding Path="..." ElementName="MySomethingElse"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
With your Converter Defined using the IMultiValueConverter Interface:
public class MyMultiConverter: IMultiValueConverter
{
public object Convert(object[] values, ...)
{
return values.Clone();
}
}
and for extracting the values:
Simply refer to the parameter in your command as an Object[] and use the parameters in the same order as in the MultiBinding.

How can i send multiple parameters from button in wpf.
You can only send one parameter as the CommandParameter.
A better solution is typically to just bind the TextBox and other controls to multiple properties in your ViewModel. The command would then have access to all of those properties (since it's in the same class), with no need for a command parameter at all.

Related

WPF/MVVM: switching between views

I'm trying to learn WPF/MVVM, and I'm currently working on how to switch between views. I've started by finding some example to study in the interntet. The one I'm using is quite simple: two views (named "Home and "Account") that only display a label, to keep the xaml and VM simple, and a main window with two buttons to switch between the views.
The DataTemplates are declared in the App.xaml file (together with the namespaces), so they should be global to the whole project:
<Application.Resources>
<DataTemplate DataType="{x:Type viewmodels:HomeViewModel}">
<views:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:AccountViewModel}">
<views:AccountView/>
</DataTemplate>
</Application.Resources>
The way I've understood it, the trick is done by a third VM (called MainViewModel.cs) that implements a SelectedViewModel attribute that keeps track of the VM that must be displayed, plus and ICommand bound to the buttons:
private BaseViewModel _selectedViewModel;
public BaseViewModel SelectedViewModel
{
get { return _selectedViewModel; }
set
{
_selectedViewModel = value;
OnPropertyChanged(nameof(SelectedViewModel));
}
}
public ICommand UpdateViewCommand { get; set; }
MainWindow.xaml looks like this:
<ContentControl Grid.Row="0" Content="{Binding SelectedViewModel}"/>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Margin="10" Width="200" Content="Home" Command="{Binding UpdateViewCommand}" CommandParameter="Home"/>
<Button Margin="10" Width="200" Content="Account" Command="{Binding UpdateViewCommand}" CommandParameter="Account"/>
</StackPanel>
In a separate class file (UpdateViewModel.cs) the UpdateViewModel class implements the Execute method as follows:
public void Execute (object parameter)
{
if (parameter.ToString() == "Home")
{
viewModel.SelectedViewModel = new HomeViewModel();
}
else if (parameter.ToString() == "Account")
{
viewModel.SelectedViewModel = new AccountViewModel();
}
}
I hope I've given the idea without boring you. It all works and lets me understand the basics. Now I wanted to try a variant, i.e. take one view (the "Account" one) and implement a button that would switch directly to the other view. I thought all I had to do was to bind the button to the UpdateViewModel class, and initially I modified the Account.xaml code as follows:
<Button Content="Button" Command="{Binding Path=UpdateViewCommand}" CommandParameter="Home"/>
The program runs, but when I click on the button in the Account view, nothing happens. So I changed this to something more complex:
<UserControl.DataContext>
<src:MainViewModel/>
</UserControl.DataContext>
...
<Button Content="Button" Command="{Binding Path=UpdateViewCommand}" CommandParameter="Home"/>
But the result is the same. I suspect it has to do with the binding, but can't see how to change it. Anybody can help?
Welcome to SO!
Your button bindings are done relative to the current DataContext, so binding to UpdateViewCommand in your AccountView (say) will try to bind to the UpdateViewCommand in your AccountViewModel, rather than your MainViewModel.
There are two ways to solve this. The first is to change your button bindings to bind to the parent's view model instead:
<Button Content="Button" Command="{Binding Path=DataContext.UpdateViewCommand, RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}" CommandParameter="Home"/>
A major disadvantage here, of course, is that the visual hierarchy your child views reside in may not always be as predictable as this.
The second (better) way is to add UpdateViewCommand handlers to each of your child view models, and then have them pass control on to whatever you actually want to handle it. In practice you would typically create a base class for all your children, to reduce code duplication, and you would create a service (e.g. INavigationService) for them to call. Your MainViewModel (say) would then implement this interface, and you would use dependency injection to inject that reference into the child view models at their moment of creation (or just have the parent pass itself directly into their constructors, if you don't want to use a full DI framework).

How can I bind a CommandParameter with a child UserControl's child element?

I have the following xaml view:
<UserControl x:Class="MyViews.PersonView"
xmlns:views="clr-namespace:MyViews"
[...]
>
[...]
<dxb:BarManager x:Name="MainBarManager">
<dxb:BarManager.Items>
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
Command="{Binding PrintPersonsCommand}"
CommandParameter="{Binding PersonsCardView, ElementName=CardUserControl}"
/>
</dxb:BarManager.Items>
<Grid>
<Grid.RowDefinitions>
[...]
</Grid.RowDefinitions>
<views:CardView x:Name="CardUserControl" Grid.Row="2"/>
</Grid>
[...]
</UserControl>
The CardView is defined as follows:
<UserControl x:Class="MyViews.CardView"
[...]>
[...]
<dxg:GridControl ItemsSource="{Binding Persons}" SelectedItems="{Binding SelectedPersons}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" SelectionMode="MultipleRow">
[...]
<dxg:GridControl.View>
<dxg:CardView x:Name="PersonsCardView"
[...]
CardTemplate="{StaticResource DisplayCardTemplate}"
PrintCardViewItemTemplate="{StaticResource PrintCardTemplate}"/>
</dxg:GridControl.View>
[...]
</dxg:GridControl>
</UserControl>
The PrintPersonsCommand is defined as follows in my ViewModel:
public class PersonViewModel
{
public PersonViewModel(...)
{
[...]
PrintPersonsCommand = new Prism.Commands.DelegateCommand<DataViewBase>(PrintPersons, CanPrintPersons);
}
public Prism.Commands.DelegateCommand<DataViewBase> PrintPersonsCommand { get; private set; }
private void PrintPersons(DataViewBase view)
{
_printService.ShowGridViewPrintPreview(view);
}
private bool CanPrintPersons(DataViewBase view)
{
return true;
}
}
Now, when I click the Print button, the above PrintPersons method is always fed with null. How do I pass CardUserControl.PersonsCardView in my MyViews.PersonView xaml above, how do I pass that PersonCardView to my command? In other words, how do I fix
CommandParameter="{Binding PersonsCardView, ElementName=CardUserControl}"
to make it work?
Currently, the only solution I've found to this problem is to replace the Command and CommandParameter with
ItemClick="OnPrintBtnClick"
and then in the PersonView's code-behind file to do:
private void OnPrintBtnClick(object sender, ItemClickEventArgs e)
{
var ctxt = DataContext as PersonViewModel;
ctxt.PrintPersonsCommand.Execute(CardUserControl.PersonsCardView);
}
That works but I can't believe there is no other way. I'm not happy with that solution because I don't have the benefits of using the Command any more, like e.g. the automatic evaluation of the Command's CanExecute method. I could also put the CardView's xaml code in the PersonView.xaml but I like my controls to be in separate files because I have the feeling it's more structured and each user control has its own responsibilities which can nicely be split into separate files. Also, that solution binds my view to my view model too tightly.
Can someone help me out please?
Without changing your existing view and viewmodel hierarchy, I was able to pass the GridControl.View to the PersonViewModel using the Tag property
You can assign the CardView to the Tag property at the bottom of your CardView UserControl, and then access this Tag as CommandParameter.
CardView UserControl
<UserControl x:Class="MyViews.CardView"
[...]>
[...]
<dxg:GridControl ItemsSource="{Binding Persons}" SelectedItems="{Binding SelectedPersons}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" SelectionMode="MultipleRow">
[...]
<dxg:GridControl.View>
<dxg:CardView x:Name="PersonsCardView"
[...]
CardTemplate="{StaticResource DisplayCardTemplate}"
PrintCardViewItemTemplate="{StaticResource PrintCardTemplate}"/>
</dxg:GridControl.View>
[...]
</dxg:GridControl>
<UserControl.Tag>
<Binding ElementName="PersonsCardView"/>
</UserControl.Tag>
</UserControl>
Print Button Xaml:
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
Command="{Binding PrintPersonsCommand}"
CommandParameter="{Binding ElementName=CardUserControl, Path=Tag}"
/>
Based on the valuable input of Insane, I came up with the following two cleaner fixes:
Code-behind solution
In the PersonView, use the ItemClick event handler on the Print button:
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
ItemClick="OnPrintBtnClick"/>
Adapt the corresponding code-behind file like this:
public partial class PersonView : UserControl
{
readonly IPrintService _printService;
public PersonView(IPrintService printService)
{
_printService = printService;
InitializeComponent();
}
private void OnPrintBtnClick(object sender, ItemClickEventArgs e)
{
_printService.ShowGridViewPrintPreview(CardUserControl.PersonsCardView);
}
}
Because I want to gray-out the Print button when there is no selection, I still need to add some code to make that happen. I can get it by
1. updating the button code to
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
ItemClick="OnPrintBtnClick" IsEnabled="{Binding CanPrintPersons}"/>
refreshing the CanPrintPersons property in the PersonViewModel upon Persons selection change
That's it.
CardViewModel solution
In that solution, we have a PersonView with its underlying PersonViewModel and a CardView with its underlying CardViewModel. I will not describe that solution with all the details as it is overkill in my situation but for the sake of completeness, I'll give the main points. Upon clicking the Print button on the PersonView, the PersonViewModel's PrintCommand is called. That command emits a Print event to the CardViewModel which in turn calls its own PrintCommand. That latter command calls
_printService.ShowGridViewPrintPreview(View);
where the View is a CardViewModel's property that is set upon CardView loading with e.g.
<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="Loaded" Command="{Binding ViewLoadedCommand}" CommandParameter="{Binding ElementName=PersonsCardView}" />
</dxmvvm:Interaction.Behaviors>
Because I have two child views I want to print, I'd need to add a view model for each one of those. In addition, those two view models plus the PersonViewModel need access to the list of Persons to be printed. In particular, they need a shared access to the same data, so that they are synchronized. A simple way to do that is explained here and is totally doable. But I think it is not worth the trouble for the simple use case I have as it adds more complexity than necessary.

WPF Trigger Button CanExecute outside ItemsControl from within ItemsControl

I'm trying to extend the app from a WPF MVVM tutorial as an exercise. I've found no solution on the net for this specific problem I'm facing here.
I have a ViewModel with an ObservableCollection called "StudentsToAdd". This collection is bound to an ItemsControl. Outside the ItemsControl I have a Button with a binding to the "AddCommand" command in the ViewModel. The relevant extract form my XAML looks as follows:
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Button Content="Add" Command="{Binding AddCommand}" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"/>
<Button Content="+" Command="{Binding AddToAddListCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="3,0,3,0" Margin="50,0,0,0"/>
<Button Content="-" Command="{Binding RemoveFromAddListCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="5,0,5,0" Margin="5,0,0,0"/>
</StackPanel>
<ItemsControl x:Name="AddList" ItemsSource="{Binding Path=StudentsToAdd}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100" Margin="0 5 3 5">
<TextBox.InputBindings>
<KeyBinding Command="{Binding ElementName=AddList, Path=DataContext.AddCommand}" Key="Return"/>
</TextBox.InputBindings>
</TextBox>
<TextBox Text="{Binding Path=LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100" Margin="0 5 3 5">
<TextBox.InputBindings>
<KeyBinding Command="{Binding ElementName=AddList, Path=DataContext.AddCommand}" Key="Return"/>
</TextBox.InputBindings>
</TextBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
The + and - buttons will add or remove students from the StudentsToAdd collection. The "AddCommand" moves all entries from StudentsToAdd to another collection called "Students" when executed.
Now what I can't get to work is this: whenever a Student in StudentsToAdd is modified (after any keystroke: UpdateSourceTrigger=PropertyChanged). I want the Add Button to evaluate the CanExecute of AddCommand in the ViewModel so its IsEnabled property is automatically set accordingly. The command methods in the ViewModel currently look as follows:
private void OnAdd()
{
foreach (Student s in StudentsToAdd)
{
Students.Add(s);
}
StudentsToAdd.Clear();
StudentsToAdd.Add(new Student { FirstName = string.Empty, LastName = string.Empty });
}
private bool CanAdd()
{
if (StudentsToAdd != null && StudentsToAdd.Count > 0)
{
return StudentsToAdd.All(x => !string.IsNullOrWhiteSpace(x.FirstName) && !string.IsNullOrWhiteSpace(x.LastName));
}
return false;
}
Does anybody know how I can achieve this without coupling parts of the MVVM?
Does anybody know how I can achieve this without coupling parts of the MVVM?
There's not much context in your question. But, it seems that you are asking how to accomplish this entirely within the view model/model layer, without involving the view layer. At least, that's what you should be asking.
If so, it should be relatively simple, assuming the code you didn't show is written reasonably. That is, since your CanAdd() method depends on property values of the Student objects, you'll need to subscribe to the Student.PropertyChanged event, and raise the ICommand.CanExecuteChanged event any time any of the Student objects' PropertyChanged event is raised.
For what it's worth, I would also encapsulate the "can be added" logic in the Student class, rather than the ViewModel class. Expose that state as a single property that the ViewModel class can check. This will address a couple of things:
Your Student class seems like the more logical place to put code that determines whether the class is ready to be added to a list of Students, and
The ViewModel class can check to make sure it's that property that is changing, so it doesn't bother to go to all the work to check all the other Student objects every time any Student property changes, and each Student object will effectively be caching the "can be added" value, so that that work to check all the other Student objects is a simple property retrieval, instead of having to re-evaluate the state every single time.
I assume you already understand how to raise the ICommand.CanExecuteChanged event, but if not, here are a couple of posts that should help you with that:
CanExecuteChanged event of ICommand
ICommand CanExecuteChanged not updating
(You'll see that there are two basic strategies: implement something in the ICommand object that will explicitly raise the ICommand.CanExecuteChanged event, or call InputManager.InvalidateRequerySuggested() to force the Input Manager to call all the CanExecute() methods it knows about. IMHO, the latter is a pretty heavy-weight and less-desirable approach, hence my suggestion to use the ICommand.CanExecuteChanged event.)

WPF Button IsEnabled Check on Multiple Bindings

I have the IsEnabled property of a XAML button configured with the following databinding currently:
<Button Name="ThirdPartyPostoneButton" Content="Postpone"
Click ="postponeThirdPartyUpdatesButton_Click" Margin="5,5,0,0"
Height="25" IsEnabled="{Binding Item3.CanDefer}"/>
I need to also add a check for IsEnabled="{Binding Item3.InstallSourceExists}" (in other words both criteria must be met in order for the button to be enabled). How can I accomplish this?
Two options I can think of:-
Use a MultiBinding plus a custom IMultiValueConverter that checks both values are true.
Expose a new property on your "Item3" model that simply returns true if the other properties are both True. This is a cleaner approach, and means that if the logic changes in the future (e.g. you need to include a third bool property), you don't have to touch your XAML.
Use a multibinding like this:
<Button Name="ThirdPartyPostoneButton" Content="Postpone" Click ="postponeThirdPartyUpdatesButton_Click" Margin="5,5,0,0" Height="25" >
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource MyCustomConvertor}">
<Binding Path="Item3.CanDefer"/>
<Binding Path="Item3.InstallSourceExists"/>
</MultiBinding>
</Button.IsEnabled>
</Button>

WPF View-ModelView Binding Need Help Please

I have been playing around and looking around on how to Bind a modelview to a view, but i cant seem to work it out.
I have a view called Search and I want to bind it to SearchModelView.
View has one button and one textbox and looks:
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<ComboBox Height="23" HorizontalAlignment="Left" Margin="12,40,0,0" Name="comboBox1" VerticalAlignment="Top" Width="174" />
<Label Content="Client:" Height="28" HorizontalAlignment="Left" Margin="0,12,0,0" Name="label1" VerticalAlignment="Top" Width="71" />
<Label Content="Client Reference:" Height="28" HorizontalAlignment="Left" Margin="0,69,0,0" Name="label2" VerticalAlignment="Top" Width="117" />
<TextBox
x:Name="clientRefTxt"
Text="{Binding Path=ClientRef, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
Height="23"
HorizontalAlignment="Left"
Margin="12,103,0,0"
VerticalAlignment="Top"
Width="174" />
<Button
Content="Search Debtors"
Height="23"
HorizontalAlignment="Left"
Margin="12,140,0,0"
Name="button1"
VerticalAlignment="Top"
Width="89"
Command="{Binding Path=SearchCommand}"/>
</Grid>
And I want it to bind to SearchViewModel:
namespace Master.ViewModel
{
public class SearchViewModel:WorkspaceViewModel
{
RelayCommand _searchCommand;
readonly Search _search;
#region Search Properties
public string ClientRef
{
get { MessageBox.Show("GET CLIENTREF"); return _search.ClientRef; }
set
{
MessageBox.Show("SET CLIENTREF");
if (value == _search.ClientRef)
return;
_search.ClientRef = value;
base.OnPropertyChanged("ClientRef");
}
}
#endregion
public ICommand SearchCommand
{
get
{
MessageBox.Show("SEARCHCOMMAND");
if (_searchCommand == null)
{
_searchCommand = new RelayCommand(
param=> this.Search(),
param=> this.CanSearch
);
}
return _searchCommand;
}
}
public void Search()
{
MessageBox.Show("SEARCHING");
}
bool CanSearch
{
get { return true; }
}
}
}
I removed all the assemblies at the top but assume that they are all there. Also note that SearchViewModel is in a separate dll, not in the exe with the View.
Any help would be great or at least a pointer in the write direction, I have already read the msdn article on MVVM and that didnt help...I kinda need a better rundown on binding those too pieces.
Thanks in Advance.
P.S.
Some more details:
SearchViewModel belongs to Master.ViewModel
SearchView is part of GUI.View
I have and idea how the binded objects work, im not to sure on how to bind the view to the viewmodel
Is your View a Grid? I've only used UserControl or Window types as Views, but you may have success using a Grid.
Regardless, this is the cleanest way to instantiate the ViewModel with a UserControl View. Just replace the UserControl tags with Grid tags if you're using a Grid.
<UserControl ...(blah blah)
xmlns:viewmodel="clr-namespace:Master.ViewModel">
<UserControl.DataContext>
<viewmodel:SearchViewModel/>
</UserControl.DataContext>
I believe keeping out of the View's code unless necessary is the preferred pattern for MVVM - let the XAML wire things up for you when possible.
You need to set the view's DataContext to an instance of the view model. There are a variety of ways of doing this, including frameworks that wire it up automagically, but the easiest way to get started is to do it in the constructor of the view:
partial class Search : Window
{
public Search()
{
InitializeComponent(); // provided by Visual Studio
DataContext = new SearchViewModel(); // all-important!
}
}
Obviously you may need to provide other information to initialise the SearchViewModel but hopefully this is enough to get you on the right track.
Your will need to bootstrap your application like #itowlson suggests.
But if you have more than one ViewModel you should allow WPF to do it for you. The basic way to do this (which is easy to maintain until you start having more than a dozen views) is to create a DataTemplate to tie the View with your ModelView(which most people call ViewModel).
So the xaml you provided is probably in a UserControl(at least it should be) so you need to do several things
First create a ResourceDictionary
(fast way is to right-click your project and click Add -> Resource Dictionary
In that file(let's name it Resources.xaml) put this :
<DataTemplate DataType="{x:Type vm:SearchViewModel}">
<vw:SearchView>
</DataTemplate>
The above is assuming you put the namespaces vw and vm for View and ViewModel namespaces respectively
Go to your App.xaml and put this:
<Application.Resources>
<ResourceDictionary Source="Resources.xaml"/>
</Application.Resources>
The above will tell WPF that whenever it encounters an object of type SearchViewModel to:
Instantiate a SearchView object
Set it's DataContext to the SearchViewModel object
HTH

Categories