Below is a very simple Prism.Wpf example with a DelegateCommand that has both Execute and CanExecute delegates.
Suppose that CanExecute depends on some property. It seems that Prism's DelegateCommand doesn't re-evaluate CanExecute condition automatically when this property changes, as RelayCommand does in other MVVM frameworks. Instead, you have to call RaiseCanExecuteChanged() explicitly in the property setter. This leads to a lot of repetitive code in any non-trivial viewmodel.
Is there a better way?
ViewModel:
using System;
using Prism.Commands;
using Prism.Mvvm;
namespace PrismCanExecute.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _name;
public string Name
{
get { return _name; }
set
{
SetProperty(ref _name, value);
// Prism doesn't track CanExecute condition changes?
// Have to call it explicitly to re-evaluate CanSubmit()
// Is there a better way?
SubmitCommand.RaiseCanExecuteChanged();
}
}
public MainWindowViewModel()
{
SubmitCommand = new DelegateCommand(Submit, CanSubmit);
}
public DelegateCommand SubmitCommand { get; private set; }
private bool CanSubmit()
{
return (!String.IsNullOrEmpty(Name));
}
private void Submit()
{
System.Windows.MessageBox.Show(Name);
}
}
}
View:
<Window x:Class="PrismCanExecute.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
Title="{Binding Title}"
Width="525"
Height="350"
prism:ViewModelLocator.AutoWireViewModel="True">
<Grid>
<!--<ContentControl prism:RegionManager.RegionName="ContentRegion" />-->
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name: " />
<TextBox Width="150"
Margin="5"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Width="50"
Command="{Binding SubmitCommand}"
Content="Submit" Margin="10"/>
<!--<Button Width="50"
Content="Cancel"
IsCancel="True" Margin="10"/>-->
</StackPanel>
</StackPanel>
</Grid>
</Window>
As #l33t explained, this is by deign. If you want the DelegateCommand to monitor VM properties for changes automatically, just use the ObservesProperty method off of the delegateCommand:
var command = new DelegateCommand(Execute).ObservesProperty(()=> Name);
This is by design. It's performance related.
Though, you can replace the Prism DelegateCommand with a custom-made command that does what you want. E.g. this implementation seems to do the trick. However, I would not recommend using it. You will very likely run into performance problems if you have lots of commands.
Also, see this answer.
Related
I have implement the command Binding on Text Box and implement the command for click action in Button. Initially focus is persists in the Text Box only. My issue is me tried to click the button on view through mouse. In this case my text box lost focus command triggered first that is fine but that the button click command is not invoked. Text Box lost focus event handled the event to traverse.
public partial class MainWindow : Window
{
public MainWindow()
{
this.DataContext = new ViewModel();
InitializeComponent();
txtServer.Focus();
}
}
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action<object> execute;
private Predicate<object> canExecute;
private event EventHandler CanExecuteChangedInternal;
public RelayCommand(Action<object> execute = null, Predicate<object> canExecute=null )
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
execute.Invoke(parameter);
}
}
public class ViewModel
{
private void ActionRequestedEvent(object param)
{
}
private ICommand _ActionCommand;
public ICommand ActionCommand
{
get
{
if (_ActionCommand == null)
this._ActionCommand = new RelayCommand(param =>
{
ActionRequestedEvent(param);
});
return _ActionCommand;
}
set
{
_ActionCommand = value;
}
}
}
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBox Width="150" Name="txtServer" Height="25">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding ActionCommand}" CommandParameter="DataSetServerLostFocus"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button Content="..." Width="25" Height="25" Margin="3 0 0 0" Command="{Binding ActionCommand}"/>
</StackPanel>
Most likely your problem is not an implementation problem, but a problem of the testing method you have chosen.
As I assume, you have set breakpoints and are trying to "catch" a call to the ActionRequestedEvent (object param) method.
When the command is executed for the first time, control is transferred to Debug Studio.
You press "Continue" and the command is not executed a second time.
This is due to the fact that after activating the Studio, your Window loses the user focus and therefore the command for the button is no longer called.
Here is an example of testing - you will see the result of calling the method in the Studio's Output Window.
using System.Diagnostics;
using System.Windows.Input;
namespace LostFocusCommand
{
public class ViewModel
{
private int num;
private void ActionRequestedEvent(object param)
{
Debug.WriteLine($"{++num}: {param}");
}
private ICommand _actionCommand;
public ICommand ActionCommand => _actionCommand
?? (_actionCommand = new RelayCommand(ActionRequestedEvent));
}
}
public partial class LostFocusCommandWindow : Window
{
public LostFocusCommandWindow()
{
InitializeComponent();
}
}
<Window x:Class="LostFocusCommand.LostFocusCommandWindow"
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:local="clr-namespace:LostFocusCommand"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
Title="LostFocusCommandWindow" Height="450" Width="800"
FocusManager.FocusedElement="{Binding ElementName=txtServer}">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBox Width="150" x:Name="txtServer" Height="25">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding ActionCommand}"
CommandParameter="DataSetServerLostFocus"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button Content="..." Width="25" Height="25" Margin="3 0 0 0"
Command="{Binding ActionCommand}"
CommandParameter="Button"/>
</StackPanel>
</StackPanel>
</Window>
I also wanted to point out to you the incorrect implementation of ICommand.
In some cases, such an implementation may not work correctly.
Use the implementation at this link: BaseInpc, RelayCommand and RelayCommand<T> classes.
I have setup a WPF with several listboxes and an add button:
<Window x:Class="QuickSlide_2._0.Window1"
x:Name="load_style"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:self="clr-namespace:QuickSlide_2._0"
xmlns:e="http://schemas.microsoft.com/expression/2010/interactivity"
Title="Load_style" Height="300" Width="300" MinHeight="720" MinWidth="1280" WindowStyle="None" AllowsTransparency="True" Background="#B0000000" AllowDrop="True" WindowStartupLocation="CenterScreen" ShowInTaskbar="False">
<Grid>
<Rectangle Height="720" HorizontalAlignment="Left" Name="rectangle1" Stroke="#00000000" VerticalAlignment="Top" Width="1280" MinHeight="320" MinWidth="380" Fill="DarkGray"/>
<ListBox Height="241" HorizontalAlignment="Left" Margin="502,371,0,0" Name="Presentation_slide_items" VerticalAlignment="Top" Width="199" />
<ListBox Name="subjects_list" Margin="74,154,1039,171" ItemsSource="{Binding ElementName=styles_list, Path=SelectedItem.subjects}"/>
<ListBox Name="sub_subjects_list" Margin="264,154,849,171" ItemsSource="{Binding ElementName=subjects_list, Path=SelectedItem.sub_subjects}"/>
<ComboBox x:Name="styles_list" HorizontalAlignment="Left" VerticalAlignment="Top" Width="120" Margin="74,112,0,0"/>
<ListBox Name="user_inputs" Margin="502,154,565,421" ItemsSource="{Binding ElementName=sub_subjects_list, Path=SelectedItem.possible_input, Mode=TwoWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Name="TextBoxList" Text="{Binding input}" BorderThickness="0" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="button_add_input" Content="Add" HorizontalAlignment="Left" Margin="502,279,0,0" VerticalAlignment="Top" Width="106" Command="{Binding ElementName=sub_subjects_list.add_input} />
</Grid>
Now I want to add an additional user_input to the list in the user_inputs listbox when clicking on the button "button_add_input". I have been searching and it looks like using the "command" option of the button could be the way to go.
This is my class "sub_subject"
public class sub_subject
{
public string short_name { get; set; }
public string name { get; set; }
public bool read_from_db { get; set; }
public string table_name { get; set; }
//public ObservableCollection<string> possible_input { get; set; }
public ObservableCollection<possible_input> possible_input { get; set; }
public sub_subject(string name)
{
this.name = name;
possible_input = new ObservableCollection<possible_input>();
//possible_input = new ObservableCollection<string>();
}
public override string ToString()
{
return this.name;
}
public void add_input()
{
possible_input input = new possible_input();
input.input = "";
possible_input.Add(input);
}
}
I was thinking I can add a function to the class that adds a possible_input to the ObservableCollection and calling this function in the command of the button. But I just cannot figure out how to setup the proper command. Any suggestions?
I assume that sub_subject is the viewmodel (the datacontext) of the window (the view).
In this case you should add a Command class to your project. This is an implementation of the ICommand. You can find an implementation of the ICommand here: https://msdn.microsoft.com/en-us/magazine/dd419663.aspx
After that you will have to create a property in your viewmodel for the command that calls the add_input method. Then, bind your command property to the Command property of the button.
I am developing a small application for learning purpose. I find that when I bind ItemControl's ItemSource to a ViewModel property in XAML, it doesn't work in an expected way. i.e. It loads the underlying collection with values at the loading time, but any changes to it are not reflected.
However, if I set Itemsource in Codebehind, it works.
When the form is loaded, it shows 2 note objects. Clicking on button should show the 3rd one. I don't understand why setting DataContext using XAML doesn't update to changes in collection. I am sharing snippet of the code here. Any help greatly appreciated.
Cut-down version of XAML -
<Window x:Class="NotesApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NotesApp"
xmlns:vm="clr-namespace:NotesApp.ViewModel"
Title="MainWindow" Height="480" Width="640">
<Window.DataContext >
<vm:MainViewModel/>
</Window.DataContext>
<DockPanel >
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl Name="NoteItemControl" ItemsSource="{Binding notes}" Background="Beige" >
<ItemsControl.LayoutTransform>
<ScaleTransform ScaleX="{Binding Value, ElementName=zoomSlider}" ScaleY="{Binding Value, ElementName=zoomSlider}" />
</ItemsControl.LayoutTransform>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Name="NoteBorder" Background="Green" CornerRadius="3" Margin="5,3,5,3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding noteText}" Margin="5,3,5,3"/>
<StackPanel Grid.Row="1" Orientation="Vertical" >
<Line X1="0" Y1="0" X2="{Binding ActualWidth,ElementName=NoteBorder}" Y2="0" Stroke="Black" StrokeThickness="1"/>
<TextBlock Text="{Binding Category}" Margin="5,3,5,3"/>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</Window>
View Code behind-
namespace NotesApp
{
public partial class MainWindow : Window
{
MainViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainViewModel();
// IT WORKS IF I BRING IN THIS STATEMENT
//NoteItemControl.ItemsSource = ViewModel.notes;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
ViewModel.AddNote(new Note("note3", "Category 3"));
}
}
}
ViewModel -
namespace NotesApp.ViewModel
{
public class MainViewModel: INotifyPropertyChanged
{
ObservableCollection<Note> _notes;
public ObservableCollection<Note> notes
{
get
{ return _notes; }
set
{
_notes = value;
OnPropertyChanged("notes");
}
}
public void AddNote(Note note)
{
_notes.Add(note);
OnPropertyChanged("notes");
}
public MainViewModel ()
{
notes = new ObservableCollection<Note>();
notes.Add(new Note("note1", "Category 1"));
notes.Add(new Note("note2", "Category 2"));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs( propertyName));
}
}
}
You create a MainViewModel instance and assign it to the MainWindow's DataContext in XAML
<Window.DataContext >
<vm:MainViewModel/>
</Window.DataContext>
The bindings in your XAML use this instance as their source object, as long as you do not explicitly specify some other source. So there is no need (and it's an error) to create another instance in code behind.
Change the MainWindow's constructor like this:
public MainWindow()
{
InitializeComponent();
ViewModel = (MainViewModel)DataContext;
}
Try this :
<Window.Resources>
<vm:MainViewModel x:Key="mainVM"/>
</Window.Resources>
Now use this key as a static resource wherever you bind something like :
<ItemsControl Name="NoteItemControl" ItemsSource="{Binding notes,Source={StaticResource mainVM},Mode=TwoWay}" Background="Beige" >
If you do this, you dont need any datacontext
Trying to learn how to bind objects to various types of controls. In this instance, I want to get sample data in my object to appear in ComboBox. The code runs but what appears instead of values (David, Helen, Joe) is text "TheProtect.UserControls.Client")
XAML: (ucDataBindingObject.xaml)
<UserControl x:Class="TheProject.UserControls.ucDataBindingObject"
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"
Width="Auto"
Height="Auto"
mc:Ignorable="d">
<Grid Width="130"
Height="240"
Margin="0">
<ComboBox Width="310"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding Path=Clients}" />
</Grid>
</UserControl>
C#: ucDataBindingObject.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
namespace TheProject.UserControls
{
public partial class ucDataBindingObject : UserControl
{
public List<Client> Clients { get; set; }
public ucDataBindingObject()
{
Clients = new List<Client>();
Clients.Add(new Client(1, "David")); // sample data
Clients.Add(new Client(2, "Helen"));
Clients.Add(new Client(3, "Joe"));
InitializeComponent();
this.DataContext = this;
}
}
C# Client.cs
using System;
using System.Linq;
namespace TheProject.UserControls
{
public class Client
{
public int ID { get; set; }
public string Name { get; set; }
public Client(int id, string name)
{
this.ID = id;
this.Name = name;
}
}
}
There are several ways to tell the framework what to display
1) Use DisplayMemberPath on the ComboBox (this will display the named property):
<ComboBox ItemsSource="{Binding Path=Clients}"
DisplayMemberPath="Name"
/>
2) Set ItemTemplate on the ComboBox. This is like #1, except allows you to define a template to display, rather than just a property:
<ComboBox ItemsSource="{Binding Path=Clients}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Green" BorderThickness="1" Padding="5">
<TextBlock Text="{Binding Path=Name,StringFormat='Name: {0}'}" />
</Border>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
3) Add a ToString() override to source class. Useful if you always want to display the same string for a given class. (Note that the default ToString() is just the class type name, which is why you see "TheProtect.UserControls.Client".)
public class Client
{
// ...
public override string ToString()
{
return string.Format("{0} ({1})", Name, ID);
}
}
4) Add a DataTemplate to the XAML resources. This is useful for associating a given class type with a more complex or stylized template.
<UserControl xmlns:local="clr-namespace:TheProject.UserControls">
<UserControl.Resources>
<DataTemplate DataType="local:Client">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</UserControl.Resources>
// ...
</UserControl>
In DisplayMemberPath, give the name of the property which you want to show in the comboBox. In SelectedValuePath, give the name of the property which you want to select. When you do a ComboBox.SelectedValue, you will get the value of this property.
Trying to get selected value from combobox returns System.Data.Entity.DynamicProxies.x
private void Button_Click(object sender, RoutedEventArgs e){
string _scanner0 = int.Parse(mycmb.SelectedValue.ToString());
string _scanner1 = mycbr.SelectedItem.ToString();
string _scanner2 = mycbr.SelectedValuePath.ToString();
string _scanner3 = mycbr.text.ToString();
}
all these Returns System.Data.Entity.DynamicProxies.x
What should i do?
How do you make a Button call ICommand.CanExecute when the command parameter is changed?
This is my current XAML.
<Button Content="Delete" Command="{Binding DeleteItemCommand}" CommandParameter="{Binding SelectedItem, ElementName=DaGrid}" />
EDIT It appears this is only an issue in WPF.
I'm not sure what you're doing wrong, but here is an example of a Button being controlled both by a BindingParameter and a CanExecute Flag. Perhaps your binding parameter isn't a DependencyProperty, and therefore, when it changes the Button isn't being notified.
<UserControl x:Class="SilverlightICommandTest.MainPage"
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:ct="clr-namespace:SilverlightICommandTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<ct:TestModel x:Key="Model" />
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Orientation="Vertical" Background="White" DataContext="{StaticResource Model}">
<CheckBox Content="Enable" IsChecked="{Binding TestCmd.CanDoCommand, Mode=TwoWay}" />
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ElementName=testSlider, Path=Value}" Width="40" Grid.Column="0" />
<Slider Name="testSlider" Minimum="0" Maximum="100" SmallChange="1" Grid.Column="1" />
</Grid>
<Button Command="{Binding TestCmd}" CommandParameter="{Binding ElementName=testSlider, Path=Value}" Content="Do Something" />
</StackPanel>
</UserControl>
And the code file:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SilverlightICommandTest
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
}
public class TestModel : DependencyObject
{
TestCommand _testCmd = new TestCommand();
public TestCommand TestCmd { get { return _testCmd; } }
public TestModel()
{
}
}
public class TestCommand : DependencyObject, ICommand
{
public static readonly DependencyProperty CanDoCommandProperty = DependencyProperty.Register("CanDoCommand", typeof(Boolean), typeof(TestCommand), new PropertyMetadata(false, new PropertyChangedCallback(CanDoCommandChanged)));
public Boolean CanDoCommand
{
get { return (Boolean)GetValue(CanDoCommandProperty); }
set { SetValue(CanDoCommandProperty, value); }
}
public event EventHandler CanExecuteChanged;
public TestCommand()
{
}
public Boolean CanExecute(Object parameter)
{
return this.CanDoCommand && (((Int32)(Double)parameter) % 2 == 0);
}
public void Execute(Object parameter)
{
MessageBox.Show("Oh Hai!");
}
private void OnCanDoCommandChanged(DependencyPropertyChangedEventArgs args)
{
if (this.CanExecuteChanged != null)
{
this.CanExecuteChanged(this, new EventArgs());
}
}
private static void CanDoCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((TestCommand)sender).OnCanDoCommandChanged(args);
}
}
}
In the future I recommend doing a little more research on the pattern first (http://www.silverlightshow.net/items/Model-View-ViewModel-in-Silverlight.aspx), and if you still can't figure it out, post more of your source code.
Strange. Normally OnCommandParameterChanged calls UpdateCanExecute (both internal methods). Does the Binding to CommandParameter work as expected?
You need to call CommandManager.InvalidateRequerySuggested to re-evaluate CanExecute. Note that it will re-evaluate it for all commands, not just the one your want...