Custom UserControl events - c#

I have an UserControl defined as follows:
<UserControl x:Class="Speaker.View.Controls.Prompt"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:conv="clr-namespace:Speaker.View.Converters"
mc:Ignorable="d" Height="Auto" Width="300" x:Name="PromptBox">
<UserControl.Resources>
<conv:VisibilityConverter x:Key="VConverter" />
</UserControl.Resources>
<Border Background="White" Padding="10" BorderThickness="1"
BorderBrush="Gray" CornerRadius="10" Height="80"
Visibility="{Binding Path=Show, ElementName=PromptBox,
Converter={StaticResource VConverter}}"
UseLayoutRounding="True">
<Border.Effect>
<DropShadowEffect BlurRadius="20" RenderingBias="Quality" />
</Border.Effect>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="10" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<TextBox x:Name="InputText" Width="Auto" Height="20"
Text="{Binding Path=InfoText, ElementName=PromptBox, Mode=OneWay}"
Grid.Row="0" BorderThickness="0" Foreground="#FF8D8D8D"
GotFocus="InputText_GotFocus" LostFocus="InputText_LostFocus" />
<Separator Grid.Row="1" />
<Button Content="{Binding Path=ButtonText, ElementName=PromptBox}" Grid.Row="2"
Width="100" Command="{Binding Path=OkCommand, ElementName=PromptBox}" />
</Grid>
</Border>
What I want to do is this:
when the user clicks on the button, I'd like to run some code (obviously :) ) - this control will be used in some other controls / windows, and the code I'd like to run will be different depending on a scenarion. So how do I bind the Command property of this button with some custom command? Example usage:
<ctrls:Prompt Show="{Binding ShouldLogIn}" ButtonText="{Binding LogInText}"
InfoText="{Binding LogInInfo}" OkCommand="what goes here???" Grid.Row="0" Grid.ZIndex="2" />
Also - I follow the MVVM patern, using the MVVMLight fw, so I'd like the solution to follow it as well.
So the question is - How do I bind to the Button.Command from outside of the prompt control?

I would also suggest making a CustomControl, but if you want to use your UserControl you will need to add a DependencyProperty in your code behind.
public partial class Prompt : UserControl
{
private bool _canExecute;
private EventHandler _canExecuteChanged;
/// <summary>
/// DependencyProperty for the OKCommand property.
/// </summary>
public static readonly DependencyProperty OKCommandProperty = DependencyProperty.Register("OKCommand", typeof(ICommand), typeof(Prompt), new PropertyMetadata(OnOKCommandChanged));
/// <summary>
/// Gets or sets the command to invoke when the OKButton is pressed.
/// </summary>
public ICommand OKCommand
{
get { return (ICommand)GetValue(OKCommandProperty); }
set { SetValue(OKCommandProperty, value); }
}
/// <summary>
/// Gets a value that becomes the return value of
/// System.Windows.UIElement.IsEnabled in derived classes.
/// </summary>
protected override bool IsEnabledCore
{
get { return base.IsEnabledCore && _canExecute; }
}
// Command dependency property change callback.
private static void OnOKCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Prompt p = (Prompt)d;
p.HookUpCommand((ICommand)e.OldValue, (ICommand)e.NewValue);
}
public Prompt()
{
InitializeComponent();
}
// Add the command.
private void AddCommand(ICommand command)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
_canExecuteChanged = handler;
if (command != null)
command.CanExecuteChanged += _canExecuteChanged;
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (OKCommand != null)
_canExecute = OKCommand.CanExecute(null);
CoerceValue(UIElement.IsEnabledProperty);
}
// Add a new command to the Command Property.
private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
{
// If oldCommand is not null, then we need to remove the handlers.
if (oldCommand != null)
RemoveCommand(oldCommand);
AddCommand(newCommand);
}
// Remove an old command from the Command Property.
private void RemoveCommand(ICommand command)
{
EventHandler handler = CanExecuteChanged;
command.CanExecuteChanged -= handler;
}
}

Related

Importing Image MVVM WPF

I'm new to WPF and while I'm trying to learn it I came across MVVM framework. Now I'm trying to implement it with a simple application i made which imports and displays an image.
XAML:
<Window x:Class="mvvmSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="1024" Height="768">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="Imported Picture">
<Image x:Name="_image" Stretch="Fill"/>
</GroupBox>
<Button Height="50" Grid.Row="1" Content="Import Picture" Click="Button_Click"/>
</Grid>
</Window>
Code Behind:
using Microsoft.Win32;
using System;
using System.Windows;
using System.Windows.Media.Imaging;
namespace mvvmSample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog open = new OpenFileDialog();
open.DefaultExt = (".png");
open.Filter = "Pictures (*.jpg;*.gif;*.png)|*.jpg;*.gif;*.png";
if (open.ShowDialog() == true)
_image.Source = new BitmapImage(new Uri(open.FileName));
}
}
}
I watched alot of tutorial on mvvm for beginners and read alot of articles about it and i grasp the concept behind it. With my application I'm assuming the view would be the same as what i have but without using events but rather using command binding for both source and button. For model, I would assume I should have an image property but I'm not sure if it should get and set the filepath or the image itself. The View Model would then contain functions for both image retrieval (OpenFileDialog) and command for the button. Are my assumptions correct or is there a better way of transforming this application into mvvm. A sample coding would be great so I can analyze it.
Thanks in advance,
In the ViewModel you should define the logic you want to execute when you click the button. To do that, you need to use a command. My suggestion is use a RelayCommand, which is a generic command:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
//[DebuggerStepThrough]
/// <summary>
/// Defines if the current command can be executed or not
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
Assuming that you are using the RelayCommand as defined above you need to supply it with one or two delegates, one that returns a bool which determines whether the command is in a valid state to be run, and a second which returns nothing and actually runs the command. If you don't supply a CanExecute delegate then the command will consider that it is always in a valid state.
In your ViewModel, you also need a property to save te Image path. This property will be bind with the Source property of the Image that you have in the View. So, your ViewModel class would be like this:
public class MainViewModel: INotifyPropertyChanged
{
private string imagePath;
public string ImagePath
{
get { return imagePath; }
set
{
imagePath = value;
SetPropertyChanged("ImagePath");
}
}
ICommand _loadImageCommand;
public ICommand LoadImageCommand
{
get
{
if (_loadImageCommand == null)
{
_loadImageCommand = new RelayCommand(param => LoadImage());
}
return _loadImageCommand;
}
}
private void LoadImage()
{
OpenFileDialog open = new OpenFileDialog();
open.DefaultExt = (".png");
open.Filter = "Pictures (*.jpg;*.gif;*.png)|*.jpg;*.gif;*.png";
if (open.ShowDialog() == true)
ImagePath = open.FileName;
}
#region Property Changed Event Handler
protected void SetPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion Property Changed Event Handler
}
With this ViewModel you don't need to do anything in the code begin of your View. You just instantiate your ViewModel in the window's resources and set the DataContext property of the root grid. After that, you can bind the properties and commands with the proper controls:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Width="1024" Height="768">
<Window.Resources>
<wpfApplication1:MainViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModel}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="Imported Picture">
<Image x:Name="_image" Stretch="Fill" Source="{Binding ImagePath}"/>
</GroupBox>
<Button Height="50" Grid.Row="1" Content="Import Picture" Command="{Binding LoadImageCommand}" />
</Grid>
To Octavioccl
I did use my own namespace in my coding as so:
<Window x:Class="mvvmSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mvvmSample="clr-namespace:mvvmSample"
xmlns:vm="clr-namespace:mvvmSample.ViewModel"
Title="MainWindow" Width="1024" Height="768">
<Window.Resources>
<vm:MainViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModel}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="Imported Picture">
<Image x:Name="_image" Stretch="Fill" Source="{Binding ImagePath}" />
</GroupBox>
<Button Height="50" Grid.Row="1" Content="Import Picture" Command="{Binding LoadedImageCommand}"/>
</Grid>
As you can see I have both:
xmlns:mvvmSample="clr-namespace:mvvmSample"
xmlns:vm="clr-namespace:mvvmSample.ViewModel"
I added the later since I cannot access the MainViewModel in Window.Resources without it and mvvmSample only have App as choice. The program runs but it doesn't do anything it just shows the UI and if i click the button nothing happens. I placed breakpoints in several places like in RelayCommand and MainWindow code behind to be able to observe the problem, but it just initialize components and shows the UI.
Update:
I was able to solve this problem which was totally my fault. I wrote the wrong function name in binding instead of LoadImage I have LoadedImage thats why my button click doesnt activate "DUH". Thanks for all your help.

How to call Command from MVVM binding

I have below code to bind ListBox data using MVVM. I would like to implment the Command from MVVM, data is binded completely and I don't know why it doesn't work with the Command. I don't receive the message when clicking on the button.
ViewModel
public class BookmarkViewModel : INotifyPropertyChanged
{
public BookmarkViewModel()
{
DataSource ds = new DataSource();
deleteBookmark = new Command(executeCommand) { Enabled = true };
_bk = ds.getBookmarkDetail();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
List<BookmarkDetail> _bk;
public List<BookmarkDetail> Bookmarks
{
get { return _bk; }
set
{
if (_bk != value)
{
_bk = value;
OnPropertyChanged("Bookmarks");
}
}
}
private Command deleteBookmark;
public Command DeleteBookmark
{
get
{
return deleteBookmark;
}
set
{
deleteBookmark = value;
}
}
void executeCommand()
{
System.Windows.MessageBox.Show(_bk[0].SuraName);
}
public class Command : ICommand
{
private readonly Action executeAction;
private bool enabled;
public bool Enabled
{
get
{
return enabled;
}
set
{
if (enabled != value)
{
enabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
}
}
}
public Command(Action executeAction)
{
this.executeAction = executeAction;
}
public bool CanExecute(object parameter)
{
return enabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
executeAction();
}
}
}
and XAML binding
<ListBox x:Name="lsbBookmarks" FontFamily="./Fonts/ScheherazadeRegOT.ttf#Scheherazade"
FlowDirection="RightToLeft"
Style="{StaticResource ListBoxStyle1}"
ItemsSource="{Binding Bookmarks}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="60"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0"
HorizontalAlignment="Stretch">
<TextBlock Padding="20,0,10,0" HorizontalAlignment="Stretch">
<Run FontSize="50" Text="{Binding ArabicText.ArabicAyaNumber}"
FontFamily="./Fonts/KPGQPC.otf#KFGQPC Uthmanic Script HAFS"
Foreground="Blue"/> <Run FontSize="30" Text="{Binding ArabicText.Aya}"/>
</TextBlock>
</StackPanel>
<Button Grid.Column="1" Tag="{Binding ArabicText.ArabicTextID}"
VerticalAlignment="Center"
Height="60" Width="50" HorizontalAlignment="Right"
Content="X" BorderBrush="Red"
Background="Red" BorderThickness="0"
Padding="0" Command="{Binding DeleteBookmark}"></Button>
</Grid>
<Line X1="0" X2="1" Y1="0" Y2="0" Stretch="Fill" VerticalAlignment="Bottom"
StrokeThickness="1" Stroke="LightGray" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Any Ideas, How to implement the Command using MVVM?
Thanks!
If I were you I would:
Move the Command implementation to a separate file or declare it outside the scope of the BookmarkViewModel class.
Use the second option as ig2r suggested. Your binding would look like this:Command="{Binding DataContext.DeleteBookmark, ElementName=lsbBookmarks}" You can also use any other ElementName other than lsbBookmarks that's defined as a parent of the ListBox.
It appears that your DeleteBookmark command is exposed as a property on the BookmarkViewModel class, whereas the actual data context within the DataTemplate used to render individual ListBox items will be an instance of BookmarkDetail. Since BookmarkDetail does not declare a DeleteBookmark command, the binding fails.
To correct this, either:
Define and expose the DeleteBookmark command on the BookmarkDetail class, or
Extend your command binding to tell the binding system where to look for the delete command, e.g., Command="{Binding DataContext.DeleteBookmark, ElementName=lsbBookmarks}" (untested).

using a button to make a user control visible in WPF using MVVM pattern

I have a grid in WPF that contains a button which should make a user control visible. How do I make this possible using MVVM pattern and /or code behind?
In your view model you want a bool property for the visibility of the user control. We'll call it IsUserControlVisible. Now you'll need a command in your view model that will set the IsUserControlVisible property to true. We'll call this ShowUserControlCommand.
In XAML you would bind the visibility of the User Control to IsUserControlVisible. In WPF there is a BooleanToVisibilityConverter, so we don't have to create our own converter. Your XAML would look something like this.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Command="{Binding ShowUserControlCommand}">Show</Button>
<UserControl Grid.Row="1" Visibility="{Binding IsUserControlVisible, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</Window>
I hope this helps.
Following a full example on how you can achieve this in MVVM with an illustration of ICommand interface.
your main should look like this
XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525"
xmlns:my="clr-namespace:WpfApplication3">
<Grid>
<my:UserControl1 Background="Aqua"
Visibility="{Binding ChangeControlVisibility,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
Margin="111,66,0,0"
x:Name="userControl11"
VerticalAlignment="Top"
Height="156"
Width="195" />
<Button Content="Button"
Height="36"
HorizontalAlignment="Left"
Margin="36,18,0,0"
Name="button1"
VerticalAlignment="Top"
Width="53"
Command="{Binding MyButtonClickCommand}" />
</Grid>
</Window>
MainWindow.cs
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MyViewModel();
}
}
}
ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
_myCommand = new MyCommand(FuncToCall,FuncToEvaluate);
}
private ICommand _myCommand;
public ICommand MyButtonClickCommand
{
get { return _myCommand; }
set { _myCommand = value; }
}
private void FuncToCall(object context)
{
//this is called when the button is clicked
//for example
if (this.ChangeControlVisibility== Visibility.Collapsed)
{
this.ChangeControlVisibility = Visibility.Visible;
}
else
{
this.ChangeControlVisibility = Visibility.Collapsed;
}
}
private bool FuncToEvaluate(object context)
{
return true;
}
private Visibility _visibility = Visibility.Visible;
public Visibility ChangeControlVisibility
{
get { return _visibility; }
set {
_visibility = value;
this.OnPropertyChanged("ChangeControlVisibility");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Command:
class MyCommand : ICommand
{
public delegate void ICommandOnExecute(object parameter);
public delegate bool ICommandOnCanExecute(object parameter);
private ICommandOnExecute _execute;
private ICommandOnCanExecute _canExecute;
public bool CanExecute(object parameter)
{
return _canExecute.Invoke(parameter);
}
public MyCommand(ICommandOnExecute onExecuteMethod, ICommandOnCanExecute onCanExecuteMethod)
{
_execute = onExecuteMethod;
_canExecute = onCanExecuteMethod;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
}
Generally speaking you have a boolean flag in your viewmodel that is bound to the user controls Visibility using an appropriate converter. You have a command in your viewmodel that is bound to the button's Command property. The Execute method of the command toggles the boolean flag.
Edit:
If you only need the button to make something visible on form, consider the Expander control that already does this out of the box.
<Expander>
<YourUserControl/>
</Expander>

How does this button click work in WPF MVVM?

I'm starting to study about WFM MVVM pattern.
But I can't understand why this Button click works without binding any event or action.
View
<Window x:Class="WPF2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:WPF2.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<vm:MainWindowViewModel x:Key="MainViewModel" />
</Window.Resources>
<Grid x:Name="LayoutRoot"
DataContext="{Binding Source={StaticResource MainViewModel}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,7,0,0" Name="txtID" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId}" />
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,35,0,0" Name="txtName" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.Name}" />
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,61,0,0" Name="txtPrice" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.Price}" />
<Label Content="ID" Grid.Row="1" HorizontalAlignment="Left" Margin="12,12,0,274" Name="label1" />
<Label Content="Price" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="12,59,0,0" Name="label2" VerticalAlignment="Top" />
<Label Content="Name" Grid.Row="1" Height="28" HorizontalAlignment="Left" Margin="12,35,0,0" Name="label3" VerticalAlignment="Top" />
<Button Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="141" Height="23" Margin="310,40,0,0" Content="Update" />
<ListView Name="ListViewProducts" Grid.Row="1" Margin="4,109,12,23" ItemsSource="{Binding Path=Products}" >
<ListView.View>
<GridView x:Name="grdTest">
<GridViewColumn Header="Product ID" DisplayMemberBinding="{Binding ProductId}" Width="100"/>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="250" />
<GridViewColumn Header="Price" DisplayMemberBinding="{Binding Price}" Width="127" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
ViewModel
class MainWindowViewModel : INotifyPropertyChanged
{
public const string ProductsPropertyName = "Products";
private ObservableCollection<Product> _products;
public ObservableCollection<Product> Products
{
get
{
return _products;
}
set
{
if (_products == value)
{
return;
}
_products = value;
RaisePropertyChanged(ProductsPropertyName);
}
}
public MainWindowViewModel()
{
_products = new ObservableCollection<Product>
{
new Product {ProductId=1, Name="Pro1", Price=11},
new Product {ProductId=2, Name="Pro2", Price=12},
new Product {ProductId=3, Name="Pro3", Price=13},
new Product {ProductId=4, Name="Pro4", Price=14},
new Product {ProductId=5, Name="Pro5", Price=15}
};
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
I found this code from a tutorial i've read. When i click on a row from listview, the textbox values are set. And when I edit those values and click a button, the values on listview are updated.
There is no event nor command bound on the button. So how do button click update the values from listview??
I know this is old but I was looking into this scenario and found my answer.
First, in MVVM theory the "code-behind" shouldn't have code all the code should be in the ViewModel class.
So in order to implement button click, you have this code in the ViewModel (besides the INotifyPropertyChanged implementation):
public ICommand ButtonCommand { get; set; }
public MainWindowViewModel()
{
ButtonCommand = new RelayCommand(o => MainButtonClick("MainButton"));
}
private void MainButtonClick(object sender)
{
MessageBox.Show(sender.ToString());
}
Using the class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace <your namespace>.ViewModels
{
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
if (execute == null) throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter ?? "<N/A>");
}
}
}
And in XAML:
<Window.DataContext>
<viewModels:MainWindowViewModel />
</Window.DataContext> <Button Content="Button" Command="{Binding ButtonCommand}" />
In above sample snippet, ListView is binded to an ObservableCollection of Product which is internally implemented INotfiyPropertyChanged Interface. This interface is responsible for raising PropertyChanged event and updates binded UI element values whenever it change.
More over you can see here, the Text property of all TextBoxes are binded as Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId}"
Binding ElementName - This is a markup extension which will tell XAML compiler to bind your ListView to Textbox control
Path - This will point to a specific property of Binded UI Element. In this case it will point to ListView.SelectedItem object property. ie. Product.ProductID
The binding mode of WPF UI element is TwoWay by default. So it will update both Source and Target whenever the value changes. You can try this by changing mode to OneWay
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,7,0,0" Name="txtID" VerticalAlignment="Top" Width="178" Text="{Binding ElementName=ListViewProducts,Path=SelectedItem.ProductId,Mode=OneWay}" />
You need to set UpdateSourceTrigger to Explicit and on the Click event of the Button you need to update the binding source.
The Buttons Click event is executed first before Command. Hence the source properties can be updated in the Click event. I have modified your code to demonstrate this.
Note:
I have written the view model in code behind for simplicity.
I have used the RelayCommand from the MVVMLight library.
1st XAML Change
<Button Grid.Row="1" HorizontalAlignment="Left" VerticalAlignment="Top" Width="141" Height="23" Margin="310,40,0,0" Content="Update" Click="Button_Click" Command="{Binding UpdateCommand}"/>
2nd XAML Change
<ListView Name="ListViewProducts" Grid.Row="1" Margin="4,109,12,23" ItemsSource="{Binding Path=Products}" SelectedItem="{Binding SelectedProduct}" >
Code behind
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WPF2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
txtID.GetBindingExpression(TextBox.TextProperty).UpdateSource();
txtName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
txtPrice.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
}
public class MainWindowViewModel : INotifyPropertyChanged
{
public const string ProductsPropertyName = "Products";
private ObservableCollection<Product> _products;
public ObservableCollection<Product> Products
{
get
{
return _products;
}
set
{
if (_products == value)
{
return;
}
_products = value;
RaisePropertyChanged(ProductsPropertyName);
}
}
private Product _SelectedProduct;
public Product SelectedProduct
{
get { return _SelectedProduct; }
set
{
_SelectedProduct = value;
RaisePropertyChanged("SelectedProduct");
}
}
private ICommand _UpdateCommand;
public ICommand UpdateCommand
{
get
{
if (_UpdateCommand == null)
{
_UpdateCommand = new RelayCommand(() =>
{
MessageBox.Show( String.Format("From ViewModel:\n\n Updated Product : ID={0}, Name={1}, Price={2}", SelectedProduct.ProductId, SelectedProduct.Name, SelectedProduct.Price));
});
}
return _UpdateCommand;
}
}
public MainWindowViewModel()
{
_products = new ObservableCollection<Product>
{
new Product {ProductId=1, Name="Pro1", Price=11},
new Product {ProductId=2, Name="Pro2", Price=12},
new Product {ProductId=3, Name="Pro3", Price=13},
new Product {ProductId=4, Name="Pro4", Price=14},
new Product {ProductId=5, Name="Pro5", Price=15}
};
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}

XAML not compiling when trying to bind commands to TextBox

I have a WPF application (same app I have written about earlier) where I'm using the event handlers shown in Samuel Jack's page at http://blog.functionalfun.net/2008/09/hooking-up-commands-to-events-in-wpf.html. I am trying to add an event handler to the TextBox in my UI to do the same action which happens when a button is pressed. However, it won't compile, the compilation error is this:
Error 2 A 'Binding' cannot be used within a 'TextBox' collection. A 'Binding' can
only be set on a DependencyProperty of a DependencyObject.
Here is the XAML (note, I want the same behaviour to happen where pressing Enter while the TextBox is in focus has the same action as clicking on the button:
Here is the View Model code:
/// <summary>
/// The ViewModel in the MVVM pattern
/// </summary>
public class ApplicationViewModel : INotifyPropertyChanged
{
#region Private members
private string searchString;
private string emailString;
private string toolName;
private NexusApp selectedApp;
private ICommand searchButtonCmd;
private ICommand clearButtonCmd;
private ICommand emailButtonCmd;
private ICommand textboxKeyCmd;
#endregion
#region INotifyPropertyChanged implementation
/// <summary>
/// Notifies the view that the observablecollection bound to the listview has changed
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Properties
/// <summary>
/// Collection bound to listview in ui view
/// </summary>
public ObservableCollection<NexusApp> AppCollection { get; set; }
/// <summary>
/// The application record selected by user
/// </summary>
public NexusApp SelectedApp
{
get
{
return this.selectedApp;
}
set
{
this.selectedApp = value;
this.emailString = this.selectedApp.Email;
this.toolName = this.selectedApp.Name;
}
}
/// <summary>
/// String entered by user into search box
/// </summary>
public string AppToSearch
{
get
{
return searchString;
}
set
{
searchString = value;
}
}
/// <summary>
/// Email address of team that owns app user is trying to request
/// </summary>
public string AppToRequest
{
get
{
return emailString;
}
set
{
emailString = value;
}
}
#endregion
/// <summary>
/// Default constructor, initialises and populates collection
/// </summary>
public ApplicationViewModel()
{
this.AppCollection = ApplicationsModel.Current;
}
#region Button command handlers
/// <summary>
/// Handles search button
/// </summary>
public ICommand SearchButtonPressed
{
get
{
if (this.searchButtonCmd == null)
{
this.searchButtonCmd = new RelayCommand(SearchButtonPressedExecute, c => CanSearch);
}
return this.searchButtonCmd;
}
}
/// <summary>
/// Handles clear button
/// </summary>
public ICommand ClearButtonPressed
{
get
{
if (this.clearButtonCmd == null)
{
this.clearButtonCmd = new RelayCommand(ClearButtonPressedExecute, c => CanClear);
}
return this.clearButtonCmd;
}
}
/// <summary>
/// Handles Request Application button
/// </summary>
public ICommand EmailButtonPressed
{
get
{
if (this.emailButtonCmd == null)
{
this.emailButtonCmd = new RelayCommand(EmailButtonPressedExecute, c => CanEmail);
}
return this.emailButtonCmd;
}
}
public ICommand TextBoxKeyPressed
{
get
{
if (this.textboxKeyCmd == null)
{
this.textboxKeyCmd = new RelayCommand(TextBoxKeyPressedExecute, c => CanSearch);
}
return this.textboxKeyCmd;
}
}
private void SearchButtonPressedExecute(object parameter)
{
try
{
ApplicationsModel.Current.Search(this.searchString);
}
catch (Exception e)
{
string error = String.Format("Could not refresh repository list: {0}", e.Message);
MessageBox.Show(error, "Error Performing Search", MessageBoxButton.OK, MessageBoxImage.Error);
}
NotifyPropertyChanged();
}
private void ClearButtonPressedExecute(object parameter)
{
try
{
this.searchString = String.Empty;
this.AppToSearch = String.Empty;
this.AppToSearch = String.Empty;
ApplicationsModel.Current.ClearSearch();
NotifyPropertyChanged();
}
catch (Exception e)
{
string error = String.Format("Could not refresh repository list: {0}", e.Message);
MessageBox.Show(error, "Error Clearing Search", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void EmailButtonPressedExecute(object parameter)
{
if (String.IsNullOrEmpty(this.toolName))
{
MessageBox.Show("Please select one of the applications to request", "Please select application", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
try
{
string message;
if (EmailHelper.IsValidEmail(this.emailString))
{
message = String.Format("Request for application {0} successfully sent", this.toolName);
}
else
{
message = String.Format("Could not send request for access\nThe email address is not defined for tool {0}.\nThe Equity Nexus team has been alerted, asking them to investigate", this.toolName);
}
ApplicationsModel.Current.Email(this.emailString, this.toolName);
MessageBox.Show(message, "Request Sent", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception e)
{
string error = String.Format("Could not send application request email, error: {0}", e.Message);
MessageBox.Show(error, "Erorr sending request", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void TextBoxKeyPressedExecute(object parameter)
{
MessageBox.Show("Hit textbox event execute");
}
/// <summary>
/// Ability to do search, always true
/// </summary>
public bool CanSearch
{ get { return true; } }
/// <summary>
/// Ability to clear search, always true
/// </summary>
public bool CanClear
{ get { return true; } }
/// <summary>
/// Ability to send email, always true
/// </summary>
public bool CanEmail
{ get { return true; } }
#endregion
}
Here is the RelayCommand code:
/// <summary>
/// Hooks up commands to the actions in Button Behaviour class
/// </summary>
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
Here is the ButtonBehaviour code:
public static class ButtonBehaviour
{
public static readonly DependencyProperty SearchCommand;
public static readonly DependencyProperty ClearCommand;
public static readonly DependencyProperty EmailCommand;
public static readonly DependencyProperty TextBoxKeyCommand;
static ButtonBehaviour()
{
SearchCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(Button.ClickEvent, "SearchCommand", typeof(ButtonBehaviour));
ClearCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(Button.ClickEvent, "ClearCommand", typeof(ButtonBehaviour));
EmailCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(Button.ClickEvent, "EmailCommand", typeof(ButtonBehaviour));
TextBoxKeyCommand = EventBehaviourFactory.CreateCommandExecutionEventBehaviour(TextBox.KeyDownEvent, "EnterKeyCommand", typeof(ButtonBehaviour));
}
public static void SetSearchCommand(DependencyObject depobj, ICommand value)
{
depobj.SetValue(SearchCommand, value);
}
public static ICommand GetSearchCommand(DependencyObject depobj)
{
return depobj.GetValue(SearchCommand) as ICommand;
}
public static void SetClearCommand(DependencyObject depobj, ICommand value)
{
depobj.SetValue(ClearCommand, value);
}
public static ICommand GetClearCommand(DependencyObject depobj)
{
return depobj.GetValue(ClearCommand) as ICommand;
}
public static void SetEmailCommand(DependencyObject depobj, ICommand value)
{
depobj.SetValue(EmailCommand, value);
}
public static ICommand GetEmailCommand(DependencyObject depobj)
{
return depobj.GetValue(EmailCommand) as ICommand;
}
public static void SetTextBoxKeyCommand(DependencyObject depobj, ICommand value)
{
depobj.SetValue(TextBoxKeyCommand, value);
}
public static ICommand GetTextBoxKeyCommand(DependencyObject depobj)
{
return depobj.GetValue(TextBoxKeyCommand) as ICommand;
}
}
XAML code:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Height="84" HorizontalAlignment="Left" Margin="0,5,5,5" Name="imgNexusLogo" Stretch="Fill" VerticalAlignment="Top" Width="600" Source="nexus1bannerlong.png" />
<Grid Grid.Row="1" HorizontalAlignment="Center" Margin="0,5,5,5" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Search for Application">
<Label.Foreground>
<SolidColorBrush Color="LightCyan" />
</Label.Foreground>
</Label>
<TextBox Grid.Row="0" Grid.Column="1" Margin="3" Width="500" Text="{Binding AppToSearch}" vm:ButtonBehaviour.TextBoxKeyCommand="{Binding TextBoxKeyPressed}" />
<Button Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" Width="100" Height="20" Margin="3" Background="LightCyan" Content="Search" ToolTip="Click to filter application list by search value" vm:ButtonBehaviour.SearchCommand="{Binding SearchButtonPressed}" />
<Button Grid.Row="0" Grid.Column="3" HorizontalAlignment="Right" Width="100" Height="20" Margin="3" Background="LightCyan" Content="Clear Search" ToolTip="Click to restore application list to full listing" vm:ButtonBehaviour.ClearCommand="{Binding ClearButtonPressed}"/>
</Grid>
<ListView Grid.Row="2" BorderBrush="Black" HorizontalAlignment="Stretch" ItemsSource="{Binding Path=AppCollection}" SelectedItem="{Binding SelectedApp}">
<ListView.View>
<GridView>
<GridViewColumn Header="Application Name" Width="200" DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Application Description" Width="631" DisplayMemberBinding="{Binding Description}"/>
<GridViewColumn Header="Application Owner" Width="150" DisplayMemberBinding="{Binding Owner}"/>
</GridView>
</ListView.View>
</ListView>
<Button Grid.Row="3" HorizontalAlignment="Center" Width="200" Height="30" Margin="3" Background="LightCyan" Content="Request Application" ToolTip="Click here to contact app owners" vm:ButtonBehaviour.EmailCommand="{Binding EmailButtonPressed}" />
</Grid>
The Event Behaviour Factory code can be found at http://blog.functionalfun.net/2008/09/hooking-up-commands-to-events-in-wpf.html, I have not made any changed to it.
Anyone know what I'm doing wrong? It's probably simple, but since I'm new to WPF and MVVM, I need to get my head around it.
Thanks!
Follow up
I just did a code behind and handled the event like that. It's not worth all of the headaches to avoid any code behinds, and this project has deadlines that have a higher priority that MVVM ideological purity. For any readers who are interested, here is what I did:
C# code:
private void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
ApplicationViewModel viewModel = DataContext as ApplicationViewModel;
if (viewModel != null)
{
viewModel.AppToSearch = textboxSearch.Text;
viewModel.SearchButtonPressedExecute(textboxSearch.Text);
}
}
}
XAML:
<TextBox Name="textboxSearch" Grid.Row="0" Grid.Column="1" Margin="3" Width="500" Text="{Binding AppToSearch}" KeyDown="TextBox_KeyDown"/>
I typically use Triggers to bind text box to a command in viewmodel.
<ComboBox Margin="5" ItemsSource="{Binding Configs}" SelectedItem="{Binding SelectedConfig, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding RetrieveSourceLayersCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
You need to add reference to this namespace in your xaml: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" and need to add appropriate assembly in you project reference. In my case I added this assembly: Choose Project > Add Reference. In the Reference Manager dialog box, on the Extensions tab, check the listing for System.Windows.Interactivity. You'll find it under Assemblies > Extensions.

Categories

Resources