This question already has answers here:
WPF Databinding: How do I access the "parent" data context?
(3 answers)
Closed 5 years ago.
Update
The button I was trying to use, is inside a <DataTemplate>, which apparently caused the issue. Once I tried the code on a button outside the the <ItemsControl> area, it works. Can anyone tell me, why it does not work in a repeated button like <ItemsControl> and <DataTemplate>?
I am trying to implement an MVVM communication pattern, based on an article from TutorialsPoints.com. I have modified the code slightly, but over all it is still very similar to the code in the article. What I want to do is, to write a line in the console once a button is clicked.
With my implementation (see code below) nothing happens when I click the button. I have also tried adding a break point in the OnClick() function to see if that is run, this is not the case. However a break point in the constructor of MyICommand() shows that the class is actually initialized. What am I doing wrong then?
The Button
<Button Content="Do stuff!"
Command="{Binding FakeCommand}"
Cursor="Hand"
Background="Red"
Foreground="White"
BorderThickness="0"
Padding="10 0 10 0" />
The View Model
public class AgreementViewModel : INotifyPropertyChanged
{
public MyICommand FakeCommand { get; set; }
public AgreementViewModel ()
{
LoadAgreements();
FakeCommand = new MyICommand(OnClick, CanClick);
FakeCommand.RaiseCanExecuteChanged();
}
private void OnClick()
{
Console.WriteLine("Something was clicked...");
}
private bool CanClick()
{
return true;
}
}
The Implementation of ICommand
public class MyICommand : ICommand
{
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public event EventHandler CanExecuteChanged = delegate {};
public MyICommand(Action executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action executeMethod, Func<bool> canExecuteMethod)
{
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null)
{
return true;
}
return false;
}
void ICommand.Execute(object parameter)
{
_TargetExecuteMethod?.Invoke();
}
}
If you have an ItemsControl (as you mention in the updated version) then the DataContext for each instantiation of the DataTemplate will each item of the source collection used in the ItemsSource. To bind to a command in the parent view model you could use ElementName to get to the ItemsControl
<ItemsControl ItemsSource="{Binding Data}" x:Name="root">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="Do stuff!"
Command="{Binding DataContext.FakeCommand, ElementName=root}"
Cursor="Hand"
Background="Red"
Foreground="White"
BorderThickness="0"
Padding="10 0 10 0" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
An alternative if you don't want to use names would be to use RelativeSource to get to the items control:
Command="{Binding DataContext.FakeCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
Note that in both cases the data context will be the ItemsControl, so you need to do DataContext.FakeCommand, DataContext refers here to the data context of ItemsControl
You might also need the item the command was invoked for since it can be invoked for any item in the source collection. To do that you can add a a CommandParameter={Binding}, and the parameter passed the command will be the item (your implementation does not pass the parameter to the delegate, but it could)
Related
I am writing a WPF app using the MVVM pattern and I am having the following problem: I have bound a command to a checkbox in my UI however my event handler is not being called when the check box is clicked. I have used the same approach to bind other UI elements such as buttons and it seems to work alright for them. The relevant xaml is as follows:
<ListBox ItemsSource="{Binding ElementsMethods}" Height="auto" x:Name="MethodsListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FormattedEM}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Started"/>
<Checkbox IsChecked="{Binding Started} Command="{Binding elementMethodCheckboxChangeCommand}"> </CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Finished"/>
<CheckBox IsChecked="{Binding Finished}"></CheckBox>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>IsChecked="{Binding Finished}
Where elementMethodCheckboxChangeCommand is a public property of type ICommand in my viewmodel class:
public ICommand elementMethodCheckboxChangeCommand { get; set; }
the concrete class used to set this property is named relay command:
elementMethodCheckboxChangeCommand = new RelayCommand(new Action<object>(elementMethodCheckboxChange));
where elementMethodCheckboxChange is a public void function taking a parameter of type object.
The implementation of the relaycommand class is as follows:
class RelayCommand : ICommand
{
private Action<object> _action;
public RelayCommand(Action<object> action)
{
_action = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
if (parameter != null)
{
_action(parameter);
}
else
{
_action("Hello world");
}
}
public event EventHandler CanExecuteChanged;
}
Like I said above I have used this same approach to bind to buttons in my UI and they have worked as expected, however when I click the checkbox nothing happens at all, and my event handler is not executed.
I hope someone can help me out here as this problem is starting to become really frustrating - please ask if you need any additional information. Thank you all in advance :)
You should specify a RelativeSource of the binding when you want to bind to a property of the view model inside an `ItemTemplate:
<CheckBox ... Command="{Binding DataContext.elementMethodCheckboxChangeCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}"/>
The default DataContext is the current item in the ItemsSource and this one has no elementMethodCheckboxChangeCommand property to bind to.
Making the property static is not a very good solution.
There are tons and tons of articles around the internet about this topic, but I just can't wrap my head around it. Most articles use code behind, but I want to stick to "pure" MVVM since I try to learn it. Also, I explicitly don't want to use any other framework (MVVMlight, Ninject...). I just want to stick to what WPF has to offer. I know this got asked a lot, but what I found either was not mvvm or was not specific enough.
My task is simple: I want to see the most simple solution of opening a modal dialog, send it a string, and get a string from the dialog back upon closing it.
Therefore I set up my MainWindow.xaml with a text input field (TextBox), a button (that should open the modal dialog) and a textblock that will show the message I intend to receive from the dialog.
The dialog has a TextBlock, showing the user-input from MainWindow.xaml, and a TextBox to enter some text, and a button. You guessed it: you press the button, and the message I typed into the textfield get's returned to MainWindow.xaml. Please refer also to the images I've included - I think it's pretty self-explanatory.
MainWindow.xaml
<Window x:Class="Dialogs.MainWindow"
...
Title="First View (Main Window)" Height="240" Width="630">
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Main View sayz: "/>
<TextBox Width="360" Margin="10,0,0,30"/>
</StackPanel>
<Button Content="Send to Second View" Command="{Binding SendToSecondViewCommand}" Width="200"/>
<StackPanel Orientation="Horizontal" Margin="10,30,10,10">
<TextBlock Text="Second View replies: "/>
<TextBlock Width="360"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
SecondView.xaml
<UserControl x:Class="Dialogs.SecondView"
...
d:DesignHeight="240" d:DesignWidth="630" Background="BlanchedAlmond">
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="This is what First View sayz: "/>
<TextBlock Width="360"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Second View replies: "/>
<TextBox Width="360" Margin="10,0,0,30"/>
</StackPanel>
<Button Content="Reply to First View" Command="{Binding ReplyToFirstViewCommand}" Width="200"/>
</StackPanel>
</Grid>
</UserControl>
Here is how I implemented INotifyPropertyChanged (It's actually a .cs file named BaseClasses; I know it's not named properly...)
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged<T>(ref T variable, T value,
[CallerMemberName] string propertyName = null)
{
variable = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And here my base class for relay commands:
public class CommandDelegateBase : ICommand
{
public delegate void ExecuteDelegate(object parameter);
public delegate bool CanExecuteDelegate(object paramerter);
private ExecuteDelegate execute;
private CanExecuteDelegate canExecute;
public CommandDelegateBase(ExecuteDelegate _execute, CanExecuteDelegate _canExecute = null)
{
execute = _execute;
canExecute = _canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return canExecute?.Invoke(parameter) ?? true;
}
public void Execute(object parameter)
{
execute.Invoke(parameter);
}
}
Lastly my ViewModels:
FirstViewModel:
public class FirstViewViewModel: NotifyPropertyChangedBase
{
private string _sendText;
public string SendText
{
get { return _sendText; }
set
{
_sendText = value;
OnPropertyChanged(ref _sendText, value);
}
}
public ICommand SendToSecondViewCommand { get; set; }
public FirstViewViewModel()
{
SendToSecondViewCommand = new CommandDelegateBase(SendExecuteCommand, SendCanExecuteCommand);
}
private bool SendCanExecuteCommand(object paramerter)
{
return true;
}
private void SendExecuteCommand(object parameter)
{
//Do stuff to :
// a) show the second view as modal dialog
// b) submit what I just wrote (SendText)
}
}
SecondViewModel:
public class SecondViewViewModel : NotifyPropertyChangedBase
{
private string _replyText;
public string ReplyText
{
get { return _replyText; }
set
{
_replyText = value;
OnPropertyChanged(ref _replyText, value);
}
}
public ICommand ReplyToFirstViewCommand { get; set; }
public SecondViewViewModel()
{
ReplyToFirstViewCommand = new CommandDelegateBase(ReplyExecuteCommand, ReplyCanExecuteCommand);
}
private bool ReplyCanExecuteCommand(object paramerter)
{
return true;
}
private void ReplyExecuteCommand(object parameter)
{
//Do stuff to :
// a) close the second view
// b) reply what I just wrote (ReplyText) back to First View.
}
}
I have a folder called "Models" in my solution but for the sake of simplicity it's empty.
I know there are solutions with helper classes or services - what ever pertains mvvm will do. I also do know that doing this for such a simple task as what I want is quiet "overkill", and has a lot more writing code coming with it than it would be justifyable for this purpose. But again: I'd like to learn this, and understand what I am doing.
Thank you so much in advance!
I wrote an article about this subject and provided a library and sample application. The article itself is long...because it's not a trivial topic...but causing a dialog box to appear can be as simple as this:
this.Dialogs.Add(new CustomDialogBoxViewModel()); // dialog box appears here
UPDATE: I just noticed that my MvvmDialogs library in that package is actually referencing MvvmLite. That's a vestigial remnant from when I was developing it though, the library itself doesn't need it, so you can remove the reference altogether.
Finding an MVVM pure solution to a programming problem, which may be straightforward in other contexts, is often not a simple task. However, creating a library of helper classes is a "write once, use many times" scenario, so no matter how much code is required, you don't have to reproduce it for every usage.
My preferred method for handling message dialogs in MVVM is a two part service module.
The View registers its data context (its ViewModel) with the DialogService as potentially wanting to display a dialog - the service will use the View's UI context to do so when it does.
The ViewModel calls the injected dialog service each time a dialog should be displayed. Calls to the MessageDialog service are made using the async / await pattern, rather than requiring some other form of callback in the ViewModel.
So now, displaying a MessageDialog from a ViewModel is as simple as
await _dialogService.ShowMessageAsync(this, "Hello from the dialog service.", perDialogIcon.Information, "Mvvm Dialog Service").ConfigureAwait(false);
or
var response = await _dialogService.ShowDialogAsync(this, perDialogButton.YesNo, "Do you want to continue?", perDialogIcon.Question, "Mvvm Dialog Service").ConfigureAwait(false);
I covered this in more detail on a blog post.
As an aside, your ViewModel properties look a bit wierd - you're setting the backing-field value, then passing it into your OnPropertyChanged() method where the value is set again.
Simple questions can be the hardest sometimes. 3 things I am trying to understand;
1. Allow a selection change within a combobox to help populate items in 2nd combobox.
2. Clear items in 2nd box before populating items.
3. Adding items in 2nd box.
Note that this code worked on my WinForms code, but I am trying to convert it to WPF and understand that code.
Code:
<ComboBox Name="ComboBox_Location" HorizontalAlignment="Left" Margin="170,56,0,0" VerticalAlignment="Top" Width="160">
<ComboBoxItem Content="Hospital"/>
</ComboBox>
<ComboBox Name="ComboBox_Printer" HorizontalAlignment="Left" Margin="30,131,0,0" VerticalAlignment="Top" Width="300"/>
$ComboBox_Location.add_SelectionChanged{
switch ($ComboBox_Location.SelectedItem){
"Hospital"{
$ComboBox_Printer.Items.Clear();
$Hospital = Get-Printer -ComputerName \\bmh01-print01 | where {($_.Name -like “*BMH01*”) -and ($_.DeviceType -eq "Print")}
foreach($Name in $Hospital){
$ComboBox_Printer.Items.Add("$($Name.name)");
}
}
}
Thank you in advance! And if any of you have a website or cite I could go to to see the specific coding for WPF, any help will be appreciated!
why is this question not answered by anyone.Anyway I will do my best to explain you. Hope I am not late to answer this question.
In WPF, we follow MVVM pattern, So there are 3 parts Model, View and ViewModel.
In viewmodel, we need to inherit Icommand and create a CommandHandler, so that if there is any button click / Selction changed will sent via this command and the delegated eventhandler will be raised.
The CommandHandler Class
public class CommandHandler : ICommand
{
private Action<object> _action;
private bool _canExeute;
public event EventHandler CanExecuteChanged;
private bool canExeute
{
set
{
_canExeute = value;
CanExecuteChanged(this, new EventArgs());
}
}
public CommandHandler(Action<object> action,bool canExecute)
{
_action = action;
_canExeute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExeute;
}
public void Execute(object parameter)
{
_action(parameter);
}
}
This CommandHandler will be used in the ViewModel Class, and then the viewmodel will be set as Datacontext to view via XAML.
public class ViewModelBase : INotifyPropertyChanged
{
private List<String> _printer = new List<string>();
private bool _canExecute;
public ViewModelBase()
{
_canExecute = true;
}
public List<string> Printers
{
get { return _printer; }
set { _printer = value; }
}
private ICommand _SelectedItemChangedCommand;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public ICommand SelectedItemChangedCommand
{
get
{
return _SelectedItemChangedCommand ?? (_SelectedItemChangedCommand =
new CommandHandler(obj => SelectedItemChangedHandler(obj), _canExecute));
}
}
public void SelectedItemChangedHandler(object param)
{
var selectedItem = ((ComboBoxItem)param).Content;
switch (selectedItem)
{
case "Hospital":
Printers = new List<string>(); //clearing the list;
// Hospital = GetHospital();// - ComputerName \\bmh01 - print01 | where { ($_.Name - like “*BMH01 *”) -and($_.DeviceType - eq "Print")}
// Here I have added data hard coded, you need to call your method and assgin it to printers property.
Printers.Add("First Floor Printer");
Printers.Add("Second Floor Printer");
OnPropertyChanged(nameof(Printers));
break;
default:
Printers = new List<string>();
break;
}
}
}
The ViewModel class is also inheriting INotifyPropertyChanged, where we need to implement the event and raise it. Now we need to raise propertychanged event providing the property name which is changed using assignment. Therefore inside SelectionChangedCommand, we add Printer and then raise PropertyChanged Event by sending the Printers PropertyName in as Parameter.
The View, We can use either Window or UserControl, For this example I have used Window.
View:-
<Window x:Class="Combo2.MainScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:Combo2"
xmlns:ViewModel="clr-namespace:Combo2.Validate"
mc:Ignorable="d"
x:Name="Window1"
Title="MainScreen" Height="450" Width="800">
<Window.DataContext>
<ViewModel:ViewModelBase/>
</Window.DataContext>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="Location" HorizontalAlignment="Left" Grid.Row="0" VerticalAlignment="Top" Grid.Column="0"/>
<ComboBox Name="ComboBox_Location" HorizontalAlignment="Left" VerticalAlignment="Top" Width="160" Grid.Row="0" Grid.Column="1" >
<ComboBoxItem Content="Hospital"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}" CommandParameter="{Binding ElementName=ComboBox_Location, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Label Content="Printer Names" HorizontalAlignment="Left" Grid.Row="1" VerticalAlignment="Top" Grid.Column="0"/>
<ComboBox Name="ComboBox_Printer" HorizontalAlignment="Left" VerticalAlignment="Top" Width="160" Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Printers}" >
</ComboBox>
</Grid>
Now, As in winform we have click or selectionchanged event but in order to keep the designer separate from code, we are not directly coupling it with it. I mean to say write a selection changed event in code behind then we are not making justification to it. For more information click on https://www.tutorialspoint.com/mvvm/mvvm_introduction.htm which will give you more insight into MVVM.
Now if you notice, when there is a selection changed we have binded the Command Property to a Command Property present in the Viewmodel, which is possible using Interaction class.
So where did we link the view and viewmodel that it at the top of the xaml. Here the datacontext is bound to ViewmodelBase class(viewmodel)
<Window.DataContext>
<ViewModel:ViewModelBase/>
</Window.DataContext>
Now answer to your question
1)Allow a selection change within a combobox to help populate items in 2nd combobox.
The selectionChanged event is called which will call the Command method present in the ViewModelBase and popluate the Printers Property.
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}" CommandParameter="{Binding ElementName=ComboBox_Location, Path=SelectedItem}"/>
Now since the viewmodel is bound to view any change to the property is displayed in the 2nd dropdown. Now that I have cleared and added data in Printers property, when the 1st drop is selected based on the text if matches "Hospital" the printers are added to the Property and displayed in 2nd Drop down.
2) Clear items in 2nd box before populating items
Before adding data in Printers property, it is cleared by instantiating the List, in your case it could be any other class. Now to whether the selected data is Hospital, we need to send the SelectedItem using the Command Parameter , we cast the "param" with ComboBoxItem and got the content.
3) Adding items in 2nd box.
We sure did add the values in Printers property.
Hope this helps you !!
I'm trying to create TabItem Headers with Buttons that enable the User to close tabs. The visual representation and the Databinding of the object is just fine.
I've experimented with the DataContext, but so far I haven't found a workable solution.
My XAML:
<TabControl
Grid.Column="3"
Grid.Row="2"
x:Name="TabControlTargets"
ItemsSource="{Binding Path=ViewModelTarget.IpcConfig.DatabasesList, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding Path=ViewModelTarget.SelectedTab, UpdateSourceTrigger=PropertyChanged}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock FontFamily="Calibri" FontSize="15" FontWeight="Bold" Foreground="{Binding FontColor}" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" Margin="0,0,20,0"/>
<Button HorizontalAlignment="Left" DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext}" Command="{Binding Path = ViewModelTarget.buttonRemoveDatabaseCommand}"
CommandParameter="**?**"
>
<Button.Content>
<Image Height="15" Width="15" Source="pack://application:,,,/Images/cancel.png" />
</Button.Content>
</Button>
</StackPanel>
</DataTemplate>
I have trouble figuring out how to set the CommandParameter of my button so that it refers to the correct object.
Here is my RelayCommand:
public ICommand buttonRemoveDatabaseCommand
{
get
{
if (_buttonRemoveDatabaseCommand == null)
{
_buttonRemoveDatabaseCommand = new RelayCommand(
param => RemoveDatabase(param)
);
}
return _buttonRemoveDatabaseCommand;
}
}
And here is my RemoveDatabase function:
public void RemoveDatabase(object dB)
{
this.IpcConfig.RemoveDataBase((PCDatabase)dB);
}
I would strongly prefer a solution that sticks to my "no code behind" approach.
As pointed in the comments, you can use CommandParameter="{Binding}" to pass the TabItem context to the command.
A better approach is though to move the command to the ViewModel of your TabItem.
Here an example implementation using Prism and Prism's EventAggregator. You can of course implement this with every other MVVM Framework or even implement it yourself, but that's up to you.
This would be your TabControl ViewModel, which contains a list of all databases or whatever it's meant to represent.
public class DatabasesViewModel : BindableBase
{
private readonly IEventAggregator eventAggregator;
public ObservableCollection<DatabaseViewModel> Databases { get; private set; }
public CompositeCommand CloseAllCommand { get; }
public DatabasesViewModel(IEventAggregator eventAggregator)
{
if (eventAggregator == null)
throw new ArgumentNullException(nameof(eventAggregator));
this.eventAggregator = eventAggregator;
// Composite Command to close all tabs at once
CloseAllCommand = new CompositeCommand();
Databases = new ObservableCollection<DatabaseViewModel>();
// Add a sample object to the collection
AddDatabase(new PcDatabase());
// Register to the CloseDatabaseEvent, which will be fired from the child ViewModels on close
this.eventAggregator
.GetEvent<CloseDatabaseEvent>()
.Subscribe(OnDatabaseClose);
}
private void AddDatabase(PcDatabase db)
{
// In reallity use the factory pattern to resolve the depencency of the ViewModel and assing the
// database to it
var viewModel = new DatabaseViewModel(eventAggregator)
{
Database = db
};
// Register to the close command of all TabItem ViewModels, so we can close then all with a single command
CloseAllCommand.RegisterCommand(viewModel.CloseCommand);
Databases.Add(viewModel);
}
// Called when the event is received
private void OnDatabaseClose(DatabaseViewModel databaseViewModel)
{
Databases.Remove(databaseViewModel);
}
}
Each tab would get one DatabaseViewModel as it's context. This is where the close command is defined.
public class DatabaseViewModel : BindableBase
{
private readonly IEventAggregator eventAggregator;
public DatabaseViewModel(IEventAggregator eventAggregator)
{
if (eventAggregator == null)
throw new ArgumentNullException(nameof(eventAggregator));
this.eventAggregator = eventAggregator;
CloseCommand = new DelegateCommand(Close);
}
public PcDatabase Database { get; set; }
public ICommand CloseCommand { get; }
private void Close()
{
// Send a refence to ourself
eventAggregator
.GetEvent<CloseDatabaseEvent>()
.Publish(this);
}
}
When you click the close Button on the TabItem, then CloseCommand would be called and send an event, that would notify all subscribers, that this tab should be closed. In the above example, the DatabasesViewModel listens to this event and will receive it, then can remove it from the ObservableCollection<DatabaseViewModel> collection.
To make the advantages of this way more obvious, I added an CloseAllCommand, which is a CompositeCommand that registers to each DatabaseViewModels CloseCommand as it's added to the Databases observable collection, which will call all registered commands, when called.
The CloseDatabaseEvent is a pretty simple and just a marker, that determines the type of payload it receives, which is DatabaseViewModel in this case.
public class CloseDatabaseEvent : PubSubEvent<DatabaseViewModel> { }
In real-world applications you want to avoid using the ViewModel (here DatabaseViewModel) as payload, as this cause tight coupling, that event aggregator pattern is meant to avoid.
In this case it's can be considered acceptable, as the DatabasesViewModel needs to know about the DatabaseViewModels, but if possible it's better to use an ID (Guid, int, string).
The advantage of this is, that you can also close your Tabs by other means (i.e. menu, ribbon or context menus), where you may not have a reference to the DatabasesViewModel data context.
I have an ItemsControl so that I can display multiple instance of the same template. I need to be able to execute code on event handlers so that I can tell controls apart.
For example: I have a list of groceries, so my DataTemplate contains a "buy" Button for each food. I want to bind said button to code and tell which button was pressed.
How can I accomplish that, considering I'm using MVVM design pattern
** XAML :**
<ItemsControl ItemsSource="{Binding MyItemList}">
<ItemsControl.ItemsTemplate>
<DataTemplate>
<Button Content="Buy" />
</DataTemplate>
</ItemsControl.ItemsTemplate>
</ItemsControl>
So, MyItemList is a List<MyItem> instance. The DataTemplate contains controls that modify values or execute code not present in MyItem:
I have read a lot of articles on biding templates to commands, but I cant find one that uses a list of items.
You need to bind the Button to a Command your ItemsControl's DataContext.
Search for Command in WPF : ( A Common implementation ) :
public class RelayCommand<T> : IRelayCommand
{
private Predicate<T> _canExecute;
private Action<T> _execute;
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
private void Execute(T parameter)
{
_execute(parameter);
}
private bool CanExecute(T parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public bool CanExecute(object parameter)
{
return parameter == null ? false : CanExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
var temp = Volatile.Read(ref CanExecuteChanged);
if (temp != null)
temp(this, new EventArgs());
}
}
In your ViewModel ( The ItemsControl's DataContext , I Hope :) )
private RelayCommand<FoodItem> _addToGroceriesCommand;
public ICommand AddToGroceriesCommand
{
get
{
if (_addToGroceriesCommand == null)
{
_addToGroceriesCommand = new RelayCommand<FoodItem>(OnAddToGroceries);
}
return _addToGroceriesCommand;
}
}
public void OnAddToGroceries(FoodItem newItem)
{
}
XAML :
<ItemsControl ItemsSource="{Binding MyItemList}">
<ItemsControl.ItemsTemplate>
<DataTemplate>
<Button Content="Buy"
Command="{Binding Path=DataContext.AddToGroceriesCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding}" />
</DataTemplate>
</ItemsControl.ItemsTemplate>
</ItemsControl>
You should never use events in DataTemplates this will make you use casting and then blow a hole in the whole MVVM pattern. A button has the Command property and you should Bind that property to a command inside your MyItem ViewModel.
If you still need to use an event (for instance you cant bind MouseDown to a command) you shoudl use the EventToCommadn Behaviour which allows you to bind an event to a command. You can read about it here: http://msdn.microsoft.com/en-us/magazine/dn237302.aspx
There are several things you might do.
<Button Content="Add" Click={Click} Tag="{Binding .}" DataContext="{Binding .}" />
DataContext="{Binding .} - sets the whole VM instance to property. You can do the same thing with the Tag property. Sometimes it is usefull to use Tag for these purposes. You can user either of them. Both will work.
public void Click(...)
{
var control = sender as FrameWorkElement;
if(control!= null)
{
var myVM = control.DataContext as MyViewModel;
myVM.DoSomethingWithMyVM();
}
}
You can create a usercontrol that would contain the grid and in the grid you reference the custom usercontrol. That's very flexible. In it's ButtonEventhandler you can access the datacontext and do what you need with it. this is much easier, but you'll have more work with notifications to parrent objects. This is better if you want to reuse this control.
Another thing you can do is to set the datacontext of the button to the whole ViewModel. A last effort solution would be to set the Tag of the button to the whole ViewModel. Better if you are not planing to reuse it.
You can also use this as a resource from the resourceDictionary.