WPF MVVM Command With Key Binding not firing - wpf

Pressing the delete key is not causing the command below to fire. One can assume DelegateCommand follows interface contracts since other instances of DelegateCommand referenced in the View are properly firing within the ViewModel.
Why?
View:
<ListBox Height="132"
Name="lbFiles"
ItemsSource="{Binding LbItems, UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
VerticalAlignment="Center"
SelectedValue="{Binding Path=ListBoxSelection}">
<ListBox.InputBindings>
<KeyBinding Key="Delete"
Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}
,Path=DataContext.CommandInputFilesDeleteSelected}" />
</ListBox.InputBindings>
ViewModel:
private DelegateCommand commandInputFilesDeleteSelected;
public ICommand CommandInputFilesDeleteSelected {
get {
if (commandInputFilesDeleteSelected == null) {
commandInputFilesDeleteSelected = new DelegateCommand(InputFilesDeleteSelected);
}
return saveCommand;
}
}
private void InputFilesDeleteSelected() { //this never fires :(
LbItems.Remove(ListBoxSelection);
}

private DelegateCommand commandInputFilesDeleteSelected;
public ICommand CommandInputFilesDeleteSelected {
get {
if (commandInputFilesDeleteSelected == null) {
commandInputFilesDeleteSelected = new DelegateCommand(InputFilesDeleteSelected);
}
return saveCommand; //changed from saveCommand to commandInputFilesDeleteSelected whoops
}
}

Related

Bind a multiple properties of a button in WPF to a class

I'm not sure if this is possible but I'm looking for a way to bind a button to a generic class that contain all the properties i will need to use. Every button needs a relay command so that would be included but all of our buttons will need to bind visibility and being enabled. Instead of having this group of properties and relay command for every button we will use within the given windows view model I was wondering if there was a way to have the button bind to a class then in our view model we reference a new instance of that class for each button needed and then be just be able to set the properties on that class to the values we need. I hope this makes sense.
There's probably a bunch of different ways to do something like this. I don't know if I'd choose to have a class instance for each button. But here's a rough/quick/dodgy example of a solution.
The main model for the form is providing the button models by way of a list. The individual button models then handle the button bindings.
EDIT: Extended the code a bit. Now includes command bindings. Also shows use of ItemsControl as suggested by #Xavier. Hope it helps.
MainWindow.xaml:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="400">
<StackPanel>
<!-- Known buttons -->
<StackPanel Margin="20">
<Button DataContext="{Binding ButtonModels[0], Mode=OneTime}" Content="{Binding LabelText}" Background="{Binding Colour}" Command="{Binding Command}" CommandParameter="{Binding CommandParameter}" />
<Button DataContext="{Binding ButtonModels[1], Mode=OneTime}" Content="{Binding LabelText}" Background="{Binding Colour}" Command="{Binding Command}" CommandParameter="{Binding CommandParameter}" />
<Button DataContext="{Binding ButtonModels[2], Mode=OneTime}" Content="{Binding LabelText}" Background="{Binding Colour}" Command="{Binding Command}" CommandParameter="{Binding CommandParameter}" />
</StackPanel>
<!-- Dynamic buttons -->
<StackPanel Margin="20">
<ItemsControl ItemsSource="{Binding ButtonModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding LabelText}" Background="{Binding Colour}" Command="{Binding Command}" CommandParameter="{Binding CommandParameter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Model();
}
}
public class Model
{
private Random rnd = new Random();
public List<ButtonModel> ButtonModels { get; private set; }
public Model()
{
this.ButtonModels = new List<ButtonModel>();
for (int i = 0; i < 5; i++)
{
this.ButtonModels.Add(new ButtonModel
{
LabelText = "Button " + (i + 1),
Command = new RelayCommand((index) => { this.ChangeColour((int)index); }),
CommandParameter = i
});
}
}
private void ChangeColour(int index)
{
this.ButtonModels[index].Colour = new SolidColorBrush(Color.FromRgb((byte)rnd.Next(50, 256), (byte)rnd.Next(50, 256), (byte)rnd.Next(50, 256)));
}
}
public class ButtonModel : ObservableObject
{
private string _LabelText;
public string LabelText { get => _LabelText; set => this.SetProperty(ref _LabelText, value); }
private Brush _Colour = new SolidColorBrush(Color.FromRgb(205, 205, 205));
public Brush Colour { get => _Colour; set => this.SetProperty(ref _Colour, value); }
private RelayCommand _Command;
public RelayCommand Command { get => _Command; set => this.SetProperty(ref _Command, value); }
private int _CommandParameter;
public int CommandParameter { get => _CommandParameter; set => this.SetProperty(ref _CommandParameter, value); }
}
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (field == null && value == null)
{
return false;
}
if (field == null || !field.Equals(value))
{
field = value;
this.RaisePropertyChangedEvent(propertyName);
return true;
}
return false;
}
protected void RaisePropertyChangedEvent(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class RelayCommand : ICommand
{
private Action<object> execute;
private Predicate<object> canExecute;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action<object> action, Predicate<object> canExecute = null)
{
this.execute = action;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
}

execute a function in a viewmodel and real time refresh of the view

I have a small problem with my MVVM application.
I have a function in a viewmodel which modify a collection. This collection is bind to the view to show a datagrid. When an user click on a button, the function modify the collection but it can take few minutes and the view is not refresh.
My question is how can I execute this function to have the view refreshed in real time ?
In another program I have used the dispatcher but it was in the code behind of the view without binding.
Thanks
Edit :
Model :
public class Composants : INotifyPropertyChanged
{
private string _nom;
public string Nom
{
get { return _nom; }
set { _nom = value; OnPropertyChanged("Nom"); }
}
}
ViewModel :
public class PageSynchroViewModel : INotifyPropertyChanged
{
public void SynchroniserComposants()
{
foreach (var comp in _selectedVersion.ListeComposants)
{
comp.Nom = "";
}
}
View (I don't put all the code):
<Page x:Class="Centre_de_synchronisation.Vues.PageSynchro"
[...]
xmlns:app="clr-namespace:Centre_de_synchronisation.Classes" mc:Ignorable="d"
d:DesignHeight="531" d:DesignWidth="778"
Title="PageSynchro" Background="{x:Null}">
<Canvas>
[...]
<DataGrid Name="GridComposants" Style="{StaticResource DatagridStyle}" ItemsSource="{Binding ListeComposants}" AutoGenerateColumns="False" Canvas.Left="12" Canvas.Top="201" Height="285" Width="754" >
<DataGrid.Columns>
<DataGridTextColumn
Header="Nom"
Binding="{Binding Nom}"
Width="150"
IsReadOnly="True"/>
[...]
</DataGrid>
<Button Name="BoutonSynchro" Style="{StaticResource MessageBoxButtonStyle}" Content="Synchroniser" Height="27" Width="107" Command="{Binding BoutonSynchro}" CommandParameter="GridComposants" Visibility="{Binding Etat, Converter={StaticResource VisibilityConverter}}"/>
</Canvas>
Try using an ObservableCollection<T> instead of the collection you are using now.
This should cause the View to be updated whenever an Item is added or removed from the collection.
Just remember when interacted with the ObservableCollection to Invoke the Dispatcher otherwise you will get Thread Access Exceptions
Here is the code I did to test this.
XAML
<Window.Resources>
<loc:MyViewModel x:Key="ViewModel" />
</Window.Resources>
<Canvas DataContext="{StaticResource ViewModel}">
<DataGrid ItemsSource="{Binding Collection}"
Width="150"
Height="200">
<DataGrid.Columns>
<DataGridTextColumn Header="Nom"
Binding="{Binding Nom}"
Width="150"
IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
<Button Command="{Binding DoStuffCommand}"
Canvas.Bottom="0"
Canvas.Right="0">Stuff</Button>
</Canvas>
ViewModel
public class MyViewModel
{
public ObservableCollection<MyModel> Collection { get; set; }
public ICommand DoStuffCommand { get; set; }
public MyViewModel()
{
this.Collection = new ObservableCollection<MyModel>();
for (int i = 0; i < 10; i++)
{
Collection.Add(new MyModel { Nom = i.ToString() });
}
this.DoStuffCommand = new RelayCommand(DoStuff);
}
private void DoStuff()
{
foreach (var item in Collection)
{
item.Nom = item.Nom + ".";
}
}
}
Model
public class MyModel : INotifyPropertyChanged
{
private string nom;
public string Nom
{
get { return nom; }
set
{
nom = value;
RaiseChanged("Nom");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaiseChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
It updated the Nom in the view.

ListBox SelectedItem Binding Broken

I am using WPF having a strange issue with RadListBox SelectedItem databinding, trying to figure out but no luck. Following is my scenario
I am using Telerik Controls (RadListBox, and RadButton)
RadButton is placed inside a ItemsControl, RadListBox and ItemsControl are bind to same ItemsSource.
I am using PRISM and MVVM.
What I want is when I click on button, the same item is selected from RadListBox automatically, (This part working fine).
Problem: As soon as I click on any item of RadListBox and then click back on any button the item selection stops working.
Edit: I tried the same thing with standard WPF ListBox by adding attached behavior for selection changed event and attached property of Command and CommandParameter, it works fine, so it looks like an issue with Telerik RadListBox ?
Now let me come to code.
ViewModel Class
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public DelegateCommand<object> StudentSelected { get; set; }
public DelegateCommand<object> ButtonPressed { get; set; }
private void OnStudentSelected(object par)
{
//Debugger.Break();
if (handled == false)
{
Student std = par as Student;
if (std != null)
{
SelectedStudent = std;
}
}
handled = false;
}
private void OnButtonPressed(object par)
{
//Debugger.Break();
handled = true;
String std = par as String;
if (std != null)
{
foreach (Student st in _students)
{
if (st.Name.Equals(std))
{
SelectedStudent = st;
break;
}
}
}
}
private Student _selectedstudent;
private bool handled = false;
public MainViewModel()
{
StudentSelected = new DelegateCommand<object>(OnStudentSelected);
ButtonPressed = new DelegateCommand<object>(OnButtonPressed);
}
public Student SelectedStudent
{
get
{
return _selectedstudent;
}
set
{
_selectedstudent = value;
OnPropertyChanged("SelectedStudent");
}
}
private ObservableCollection<Student> _students;
public ObservableCollection<Student> Students
{
get
{
return _students;
}
set
{
_students = value;
OnPropertyChanged("Students");
}
}
}
public class Student
{
public String Name { get; set; }
public String School { get; set; }
}
MainView XAML
<telerik:RadListBox Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Students}" Command="{Binding StudentSelected}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=SelectedItem}" SelectedItem="{Binding SelectedStudent, Converter={StaticResource DebugConverter}}">
<!-- The above debug converter is just for testing binding, as long as I keep on clicking button the Converter is being called, but the moment I click on RadListBoxItem the Converter is not called anymore, even when I click back on buttons -->
<telerik:RadListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</telerik:RadListBox.ItemTemplate>
</telerik:RadListBox>
<Label Grid.Row="0" Grid.Column="1" Content="{Binding SelectedStudent.Name}"></Label>
<StackPanel Grid.Column="1" Grid.Row="1" Orientation="Horizontal">
<ItemsControl ItemsSource="{Binding Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadButton Width="100" Height="70" Content="{Binding Name}" Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.ButtonPressed}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=Content}">
</telerik:RadButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
and finally populating the ViewModel and setting the Datacontext
MainViewModel mvm = new MainViewModel();
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { Name = "Student 1", School = "Student 1 School" });
students.Add(new Student { Name = "Student 2", School = "Student 2 School" });
students.Add(new Student { Name = "Student 3", School = "Student 3 School" });
mvm.Students = students;
//Bind datacontext
this.DataContext = mvm;
Please give your suggestions and share you expertise from WPF Jargon?
Finally I figured out the issue, I just need to replace the RadListBox SelectedItem binding to TwoWay
<telerik:RadListBox Grid.Column="0" Grid.Row="0" ItemsSource="{Binding Students}" Command="{Binding StudentSelected}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}, Path=SelectedItem}" SelectedItem="{Binding SelectedStudent, Mode,TwoWay, Converter={StaticResource DebugConverter}}">

How to fire event from ComboBox inside StackPanel in WPF and MVVM

I have ComboBox inside StackPanel. I am using MVVM and try to bind 'GotFocus' event Command to Command in ViewModel but when I Click on 'ComboBox', it don't work (It don't call Command in ViewModel) but if I move that 'ComboBox' out of 'StackPanel' it's working properly.
How can I fire event from 'CombBox' inside 'StackPanel' in MVVM?
<StackPanel x:Name="StackPanel" Grid.Column="2" Grid.Row="6">
<ComboBox x:Name="ComboBox" ItemsSource="{Binding Values}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<cmd:EventToCommand Command="{Binding Path=GotFocusCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
ViewModel's code is:
public ViewModelCommand GotFocusCommand { get; set; }
////Change your tag from EventToCommand to InvokeCommandAction
<StackPanel x:Name="StackPanel" Grid.Column="2" Grid.Row="6">
<ComboBox x:Name="ComboBox" ItemsSource="{Binding Values}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<cmd:InvokeCommandAction="{Binding Path=GotFocusCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
////Then, I use my commands this way in my view model:
private ICommand _GotFocusCommand;
public ICommand GotFocusCommand
{
get
{
if (_GotFocusCommand == null)
{
_GotFocusCommand =
new RelayCommand(
param => GotFocusCommand_Executed(),
GotFocusCommand_CanExecute
);
}
return _GotFocusCommand;
}
}
////RelayCommandClass.cs:
public class RelayCommand : ICommand
{
private Action _handler;
public RelayCommand(Action handler)
{
_handler = handler;
}
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler();
}
}
////Finally, you can create an event in your view model:
private void GotFocusCommand_Executed()
{
//DoSomething here
}
private bool GotFocusCommand_CanExecute()
{
return true;
}

Silverlight MVVM Light Listbox item Click Event

I'm using the MVVM Light Toolkit with Silverlight.
On my UserControl I have a ListBox that displays a list of files. Each file has a delete image next to the file name. In the DataTemplate for the listbox I have an image (or can use a button) and a TextBlock.
So I want to capture using the event when the user will clicks on the image(or button with image) to remove the file from the list of files.
But I cannot seem to capture the event. Maybe this is due to having the SelectedItem Event on the listbox?
public class MainViewModel : ViewModelBase
{
#region Properties
public const string SelectedListBoxFilePropertyName = "SelectedUploadFile";
private UploadFile _selectedUploadFile = null;
public UploadFile SelectedUploadFile
{
get
{
return _selectedUploadFile;
}
set
{
if (_selectedUploadFile == value)
return;
_selectedUploadFile = value;
RaisePropertyChanged(SelectedListBoxFilePropertyName);
}
}
public const string UploadFilesPropertyName = "UploadFiles";
private ObservableCollection<UploadFile> _uploadFiles = new ObservableCollection<UploadFile>();
public ObservableCollection<UploadFile> UploadFiles
{
get
{
return _uploadFiles;
}
set
{
if (_uploadFiles == value)
return;
_uploadFiles = value;
RaisePropertyChanged(UploadFilesPropertyName);
}
}
#endregion
public static ICommand BrowseCommand { get; private set; }
public static ICommand DragDropFileCommand { get; private set; }
public static ICommand RemoveCommand { get; private set; }
#region Constructor
public MainViewModel()
{
if (IsInDesignMode)
{
// Code runs in Blend --> create design time data.
UploadFiles = new UploadFileContainer().UploadFiles;
}
else
{
// Code runs "for real"
}
WireUpCommands();
}
#endregion
#region Event Handlers
private void OnBrowseFileCommand()
{
var dialog = new OpenFileDialog();
dialog.ShowDialog();
if (dialog.Files != null)
AddFiles(dialog.Files);
}
private void OnDropFileCommand(DragEventArgs e)
{
var files = e.Data.GetData(DataFormats.FileDrop) as FileInfo[];
AddFiles(files);
}
private void OnRemoveFileCommand()
{
UploadFiles.Remove(_selectedUploadFile);
}
#endregion
#region Private Methods
private void WireUpCommands()
{
BrowseCommand = new RelayCommand(OnBrowseFileCommand);
DragDropFileCommand = new RelayCommand<DragEventArgs>(e => OnDropFileCommand(e));
RemoveCommand = new RelayCommand(OnRemoveFileCommand);
UploadCommand = new RelayCommand(OnClickUploadCommand);
}
#endregion
}
<ListBox Grid.Row="1" Height="214" HorizontalAlignment="Left" AllowDrop="True" Margin="6,26,0,0" Name="UploadFilesListBox" VerticalAlignment="Top" Width="415" ItemsSource="{Binding Path=UploadFiles}" SelectedItem="{Binding Path=SelectedListBoxFile, Mode=TwoWay}" ScrollViewer.VerticalScrollBarVisibility="Auto">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Drop">
<cmd:EventToCommand Command="{Binding DragDropFileCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.Background>
<ImageBrush ImageSource="/FileUploadApplication;component/Resources/dragdrophere.png" Stretch="None" />
</ListBox.Background>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveCommand}">
<Image Source="/FileUploadApplication;component/Resources/delete.png"/>
</Button>
<Image Source="/FileUploadApplication;component/Resources/delete.png">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cmd:EventToCommand Command="{Binding RemoveCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Image> <TextBlock Text=" " />
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Since your ItemsSource is UploadFiles it's probably sending the event to UploadFile and not the view model the user control is bound to.
Your button is the element of the ItemTemplate. you're binding the listbox ItemsSource to the ObservableCollection. Every Itemtemplate DataContext is no MainViewModel, but UploadFile, which has no RemoveCommand.
I was solving this by adding to every item the parent object using constructor. RemoveCommand was inside the item's ViewModel and insede the remove function i was calling the parent's method to delete the item.
Not sure if that's the best solution but it worked for me.

Resources