I've started using databinding with MVVM pattern in Silverlight. There's one problem for me.
I have a listbox, when I select an item there one property in the View-Model have to change it's value. I implemented DelegateCommand and made custom CommandListBox class as follows
public class DelegateCommand<T> : ICommand
{
private readonly Action<T> _execute;
private readonly Predicate<T> _canExecute;
public DelegateCommand(Action<T> execute)
: this(execute, x => true)
{
}
public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
{
if (canExecute == null) throw new ArgumentNullException("canExecute");
if (execute == null) throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public bool CanExecute(object parameter)
{
return _canExecute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged.DynamicInvoke(this);//.Raise(this);
}
}
public class DelegateCommand : DelegateCommand<object>
{
public DelegateCommand(Action execute)
: base(execute != null ? x => execute() : (Action<object>)null)
{
}
public DelegateCommand(Action execute, Func<bool> canExecute)
: base(execute != null ? x => execute() : (Action<object>)null,
canExecute != null ? x => canExecute() : (Predicate<object>)null)
{
}
}
public class CommandListBox : ListBox
{
public CommandListBox()
{
SelectionChanged += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
}
public static DependencyProperty CommandProperty =
DependencyProperty.Register("Command",
typeof(ICommand), typeof(CommandListBox),
new PropertyMetadata(null, CommandChanged));
private static void CommandChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
{
var treeList = source as CommandListBox;
if (treeList == null) return;
treeList.RegisterCommand(args.OldValue as ICommand, args.NewValue as ICommand);
}
private void RegisterCommand(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
oldCommand.CanExecuteChanged -= HandleCanExecuteChanged;
if (newCommand != null)
newCommand.CanExecuteChanged += HandleCanExecuteChanged;
HandleCanExecuteChanged(newCommand, EventArgs.Empty);
}
private void HandleCanExecuteChanged(object sender, EventArgs args)
{
if (Command != null)
IsEnabled = Command.CanExecute(CommandParameter);
}
public ICommand Command
{
get { return GetValue(CommandProperty) as ICommand; }
set { SetValue(CommandProperty, value); }
}
public static DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter",
typeof(object), typeof(CommandListBox),
new PropertyMetadata(null));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
I'm catching the event, when the selected item in listbox is changed but I don't know how to take it's value.
Could you help me please?
Your CommandListBox derives from a ListBox, it exposes a SelectionChanged event, you're attaching to that event already.
SelectionChanged += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
In order to obtain the currently selected item you can do something like this :
SelectionChanged += (sender, e) =>
{
CommandListBox source = sender as CommandListBox; // This is the sender
if(source != null) // just to be sure
{
var value = source.SelectedItem;
}
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
Does this help ?
You can bind your listbox's SelectedItem property to an appropriate property in your ViewModel.
Related
I've created a base view model that implements ICommand. The commands bind just fine, execute as expected, and even begin in the correct states, but as properties that influence whether a command can execute or not change, the CanExecute for those commands doesn't seem to be updating.
In my code below, I can click the Run button and everything works as expected EXCEPT for the fact that when the ProgramStatus changes to Running it should be disabled.
In my base view model:
public class RelayCommand : ICommand
{
#region ICommand Member Variables
private Action<object> _execute;
private Predicate<object> _canExecute;
private event EventHandler _canExecuteChangedInternal;
#endregion // ICommand Member Variables
#region Constructors
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public RelayCommand(Action<object> execute) : this(execute, DefaultCanExecute)
{
}
#endregion // Constructors
#region ICommand Members
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
_canExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
_canExecuteChangedInternal -= value;
}
}
public bool CanExecute(object parameter)
{
return _canExecute != null && _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void OnCanExecuteChanged()
{
EventHandler eventHandler = _canExecuteChangedInternal;
if (eventHandler != null)
{
eventHandler.Invoke(this, EventArgs.Empty);
}
}
private static bool DefaultCanExecute(object parameter)
{
return true;
}
#endregion // ICommand Members
In my view model:
RelayCommand _runCommand;
public RelayCommand RunCommand
{
get
{
if (_runCommand == null)
{
_runCommand = new RelayCommand(param => Run(), param => CanRun);
}
return _runCommand;
}
}
public bool CanRun
{
get
{
bool result = false;
if (Machine.ProgramStatus != ProgramStatus.Running && Machine.ProgramStatus != ProgramStatus.TestRunning)
{
result = true;
}
return result;
}
}
In my view:
<Button Content="Run" Command="{Binding Path=RunCommand}" />
You have to invoke the CanExecuteChanged EventHandler. So when ProgramStatus changes to Running you can call the OnCanExecuteChanged helper method for the command you want to update (e.g. RunCommand.OnCanExecuteChanged();).
If CanExecute is bound to a property you can use the OnCanExecuteChanged helper method in the property's setter.
private bool _myProperty;
public bool MyProperty
{
get { return _myProperty; }
set
{
_myProperty= value;
RunCommand.OnCanExecuteChanged();
}
}
I created my own DataGrid which implements a RowClick Event.
However while trying to bind a Command to it, it'll throw the exception:
{"The event \"RowClick\" on type \"ExtendedDataGrid\" has an incompatible signature. Make sure the event is public and satisfies the EventHandler delegate."}
Since I am new to MVVM my already hurts from all the Input I got in the last couple days about MVVM..Can someone hint me the (mostly) obvious error?
Thanks in advance
Here's my (testproject) code:
public class ExtendedDataGrid : DataGrid
{
public event EventHandler<DataGridRow> RowClick;
public ExtendedDataGrid()
{
this.DefaultStyleKey = typeof(DataGrid);
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.PreviewKeyDown += RowOnKeyDown;
row.MouseLeftButtonUp += RowOnMouseLeftButtonUp;
base.PrepareContainerForItemOverride(element, item);
}
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.KeyUp -= RowOnKeyDown;
row.MouseLeftButtonUp -= RowOnMouseLeftButtonUp;
base.ClearContainerForItemOverride(element, item);
}
private void RowOnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
mouseButtonEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
private void RowOnKeyDown(object sender, KeyEventArgs keyEventArgs)
{
if (keyEventArgs.Key != Key.Enter)
return;
keyEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
protected virtual void OnRowClick(DataGridRow clickedRow)
{
if (null == this.RowClick)
return;
this.RowClick(this, clickedRow);
}
}
Window.xaml
<controls1:ExtendedDataGrid x:Name="extGrid">
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowClick" SourceObject="{Binding ElementName=extGrid}">
<i:InvokeCommandAction Command="{Binding MyCommand}" CommandParameter="{Binding SelectedItem,ElementName=extGrid}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<controls1:ExtendedDataGrid.Items>
<TextBlock Text="Text" />
</controls1:ExtendedDataGrid.Items>
</controls1:ExtendedDataGrid>
window.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this._selectCommand = new DelegateCommand<DataGridRow>(x =>
{
});
//following works fine..
this.extGrid.RowClick += (s, e) =>
{
};
}
private DelegateCommand<DataGridRow> _selectCommand;
public ICommand MyCommand
{
get
{
return this._selectCommand;
}
}
}
DelegateCommand Implementation:
public class DelegateCommand<T> : DelegateCommand
{
public DelegateCommand(Action<T> executeHandler)
: this(null, executeHandler)
{ }
public DelegateCommand(Func<T, bool> canExecuteHandler, Action<T> executeHandler)
: base(o => null == canExecuteHandler || canExecuteHandler((T)o), o => executeHandler((T)o))
{
if (null == executeHandler)
throw new ArgumentNullException("executeHandler");
}
}
/// <summary>
/// Stellt ein standard DelegateCommand dar.
/// </summary>
public class DelegateCommand : ICommand
{
#region Events
public event EventHandler CanExecuteChanged;
#endregion
#region Variablen
private readonly Action<object> _executeHandler;
private readonly Func<object, bool> _canExecuteHandler;
private bool _isExecuting = false;
#endregion
#region Eigenschaften
public bool IsSingleExecution { get; set; }
#endregion
#region Konstruktor
public DelegateCommand(Action<object> executeHandler)
: this(null, executeHandler)
{ }
public DelegateCommand(Func<object, bool> canExecuteHandler, Action<object> executeHandler)
{
if (null == executeHandler)
throw new ArgumentNullException("executeHandler");
this._executeHandler = executeHandler;
this._canExecuteHandler = canExecuteHandler;
}
#endregion
#region Public Methoden
public virtual bool CanExecute(object parameter)
{
return (!this.IsSingleExecution || (this.IsSingleExecution && !this._isExecuting)) && (null == this._canExecuteHandler || this._canExecuteHandler(parameter));
}
public virtual void Execute(object parameter)
{
if (this.CanExecute(parameter))
{
this._isExecuting = true;
this.RaiseCanExecuteChanged();
try
{
this._executeHandler(parameter);
}
finally
{
this._isExecuting = false;
this.RaiseCanExecuteChanged();
}
}
}
public void RaiseCanExecuteChanged()
{
if (null != CanExecuteChanged)
CanExecuteChanged(this, EventArgs.Empty);
}
#endregion
The problem is coming from this line:
<i:EventTrigger EventName="RowClick" SourceObject="{Binding ElementName=extGrid}">
The EventTrigger class is expecting a routed event which uses the RoutedEventHandler delegate not the EventHandler delegate.
These are the changes you have to make in your code to make it work:
In ExtendedDataGrid:
public class ExtendedDataGrid : DataGrid
{
public static readonly RoutedEvent RowClickEvent = EventManager.RegisterRoutedEvent("RowClick",
RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ExtendedDataGrid));
public event RoutedEventHandler RowClick
{
add { AddHandler(RowClickEvent, value); }
remove { RemoveHandler(RowClickEvent, value); }
}
public ExtendedDataGrid()
{
this.DefaultStyleKey = typeof(DataGrid);
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.PreviewKeyDown += RowOnKeyDown;
row.MouseLeftButtonUp += RowOnMouseLeftButtonUp;
base.PrepareContainerForItemOverride(element, item);
}
protected override void ClearContainerForItemOverride(DependencyObject element, object item)
{
var row = (DataGridRow)element;
row.KeyUp -= RowOnKeyDown;
row.MouseLeftButtonUp -= RowOnMouseLeftButtonUp;
base.ClearContainerForItemOverride(element, item);
}
private void RowOnMouseLeftButtonUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
mouseButtonEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
private void RowOnKeyDown(object sender, KeyEventArgs keyEventArgs)
{
if (keyEventArgs.Key != Key.Enter)
return;
keyEventArgs.Handled = true;
this.OnRowClick((DataGridRow)sender);
}
protected virtual void OnRowClick(DataGridRow clickedRow)
{
var args = new RowClickRoutedEventArgs(clickedRow);
args.RoutedEvent = RowClickEvent;
RaiseEvent(args);
}
}
Here I removed the previous RowClick event and changed the OnRowClick method.
Add a new class called RowClickRoutedEventArgs:
public class RowClickRoutedEventArgs : RoutedEventArgs
{
public RowClickRoutedEventArgs(DataGridRow dataGridRow)
{
Row = dataGridRow;
}
public DataGridRow Row { get; set; }
}
I want to create a custom class of a CommandBinding, where a RelayCommand of my ViewModel is executed when the RoutedCommand is executed.
Currently there is only the possibility to create CommandBindings which have Executed methods in the codebehind class.
Example:
<CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandHandler"
CanExecute="CanExecuteHandler"/>
This needs the CloseCommandHandler methode in the code behind.
I would want to write the following.
<CommandBinding RoutedCommand="ApplicationCommands.Close" Command={Binding Path=CloseCommand}/>
The only problem is that i can't find the bubble down and up event of the RoutedCommands.
There is no
OnPreviewCommand(object command, object commandParammeter)
OnCommand(object command, object commandParammeter)
Where is the RoutedCommand bubble down and up handled?
I came up with a solutions of my own. Its not the most beautiful, but its working.
I derived from the ContentControl. The new Control has a RoutedCommandBindings property, which contains a list of "sort of" CommandBindings between RoutedCommands and RelayCommands.
It can be used like this.
<CSControls:RoutedCommandBinder>
<CSControls:RoutedCommandBinder.RoutedCommandBindings>
<CSControls:RoutedCommandBindingCollection>
<CSControls:RoutedCommandBinding RoutedCommand="{x:Static ApplicationCommands.New}" Command="{Binding Path=AddInstanceCommand}"/>
</CSControls:RoutedCommandBindingCollection>
</CSControls:RoutedCommandBinder.RoutedCommandBindings>
<CSControls:RoutedCommandBinder.Content>
<!-- Every RoutedCommand of type ApplicationCommands.New will execute the binded RelayCommand "AddInstanceCommand-->
</CSControls:RoutedCommandBinder.Content>
</CSControls:RoutedCommandBinder>
Here is the CustomControl code.
public class RoutedCommandBinder : ContentControl
{
public RoutedCommandBinder()
{
this.DataContextChanged += RoutedCommandBinder_DataContextChanged;
}
public static readonly DependencyProperty RoutedCommandBindingsProperty = DependencyProperty.Register("RoutedCommandBindings", typeof(RoutedCommandBindingCollection), typeof(RoutedCommandBinder), new PropertyMetadata(new RoutedCommandBindingCollection(), OnRoutedCommandBindingsChanged));
public RoutedCommandBindingCollection RoutedCommandBindings
{
get { return (RoutedCommandBindingCollection)this.GetValue(RoutedCommandBindingsProperty); }
set { SetValue(RoutedCommandBindingsProperty, value); }
}
private static void OnRoutedCommandBindingsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RoutedCommandBinder binder = (RoutedCommandBinder)d;
binder.CommandBindings.Clear();
SetDataContextForCommandBindings(binder);
if (e.NewValue != null)
{
RoutedCommandBindingCollection bindings = (RoutedCommandBindingCollection)e.NewValue;
foreach (RoutedCommandBinding binding in bindings)
{
binder.CommandBindings.Add(new CommandBinding(binding.RoutedCommand, binder.RoutedCommandExecuted, binder.CanExecuteRoutedEventHandler));
}
}
}
private void RoutedCommandBinder_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
SetDataContextForCommandBindings(this);
}
private static void SetDataContextForCommandBindings(RoutedCommandBinder binder)
{
if (binder.DataContext != null && binder.RoutedCommandBindings != null)
{
foreach (RoutedCommandBinding binding in binder.RoutedCommandBindings)
{
binding.DataContext = binder.DataContext;
}
}
}
private void RoutedCommandExecuted(object sender, ExecutedRoutedEventArgs e)
{
RoutedCommandBinding binding = this.RoutedCommandBindings.FirstOrDefault(t => t.RoutedCommand == e.Command);
if (binding != null)
{
binding.Command.Execute(e.Parameter);
}
}
private void CanExecuteRoutedEventHandler(object sender, CanExecuteRoutedEventArgs e)
{
RoutedCommandBinding binding = this.RoutedCommandBindings.FirstOrDefault(t => t.RoutedCommand == e.Command);
if (binding != null)
{
e.CanExecute = binding.Command.CanExecute(e.Parameter);
}
}
}
public class RoutedCommandBindingCollection : List<RoutedCommandBinding>
{
}
public class RoutedCommandBinding : FrameworkElement
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(RoutedCommandBinding), new PropertyMetadata(null));
public ICommand Command
{
get { return (ICommand)this.GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty RoutedCommandProperty = DependencyProperty.Register("RoutedCommand", typeof(RoutedCommand), typeof(RoutedCommandBinding), new PropertyMetadata(null));
public RoutedCommand RoutedCommand
{
get { return (RoutedCommand)this.GetValue(RoutedCommandProperty); }
set { SetValue(RoutedCommandProperty, value); }
}
}
I am using Phone7.Fx R1
The following works. The system does not react when a user presses the button. This means, than there is no reaction if Stop Game is pressed without a game has been started and vice versa.
However the button looks active. I am aware that I can bind the IsEnabled to a different property, but I would like it to bind to NewGameCanExecute and StopGameCanExecute. Is this possible?
Some XAML code:
<Preview:BindableApplicationBarIconButton Command="{Binding NewGame}" IconUri="/images/icons/appbar.add.rest.png" Text="New game" />
<Preview:BindableApplicationBarIconButton Command="{Binding StopGame}" IconUri="/images/icons/appbar.stop.rest.png" Text="Stop game" />
And the relay commands:
public RelayCommand NewGame { get; private set; }
public RelayCommand StopGame { get; private set; }
//Constructor
NewGame = new RelayCommand(NewGameExecute, NewGameCanExecute);
StopGame = new RelayCommand(StopGameExecute, StopGameCanExecute);
void NewGameExecute()
{
_gameStarted = true;
_gameControlModel.StartNewGame();
StopGame.RaiseCanExecuteChanged();
}
bool NewGameCanExecute()
{
return !_gameStarted;
}
void StopGameExecute()
{
_gameControlModel.StopGame();
_gameStarted = false;
NewGame.RaiseCanExecuteChanged();
}
bool StopGameCanExecute()
{
return _gameStarted;
}
Couple of questions and answers:
Q: Have you tried to set a breakpoint in the CanExecute functions to see if it gets called?
A: Yes. It does get called, but the icon is not grayed out, eventhough false is returned.
The Execute method is not executed, if the CanExecute method returns false. But I want the icon to be grayed out like a normal button.
SOLUTION
I spend some time and came up with a solution, which can be found here:
http://pastebin.com/MM75xACj
This is obviously a bug in whatever BindableApplicationBarIconButton implementation you're using.
Ask the author of it for help, or debug your 3rd party software yourself.
SOLUTION
I spend some time and came up with a solution and edited the applicationbariconbutton class.
namespace Phone7.Fx.Controls
{
public class BindableApplicationBarIconButton : FrameworkElement, IApplicationBarIconButton
{
public int Index { get; set; }
public static DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(BindableApplicationBarIconButton), new PropertyMetadata(null, OnCommandPropertyChanged));
private static void OnCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
((BindableApplicationBarIconButton)d).Command = (ICommand)e.NewValue;
}
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set {
Command.CanExecuteChanged -= ValueOnCanExecuteChanged;
SetValue(CommandProperty, value);
Command.CanExecuteChanged += ValueOnCanExecuteChanged;
}
}
private void ValueOnCanExecuteChanged(object sender, EventArgs eventArgs)
{
ICommand commandSender = sender as ICommand;
if(commandSender != null)
{IsEnabled = commandSender.CanExecute(null);}
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(BindableApplicationBarIconButton), new PropertyMetadata(null, OnCommandParameterPropertyChanged));
private static void OnCommandParameterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var invokeCommand = d as BindableApplicationBarIconButton;
if (invokeCommand != null)
{
invokeCommand.SetValue(CommandParameterProperty, e.NewValue);
}
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set
{
SetValue(CommandParameterProperty, value);
}
}
public static readonly DependencyProperty CommandParameterValueProperty =
DependencyProperty.RegisterAttached("CommandParameterValue", typeof(object), typeof(BindableApplicationBarIconButton), null);
public object CommandParameterValue
{
get
{
var returnValue = GetValue(CommandParameterValueProperty);
return returnValue;
}
set { SetValue(CommandParameterValueProperty, value); }
}
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(BindableApplicationBarIconButton), new PropertyMetadata(true, OnEnabledChanged));
private static void OnEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
((BindableApplicationBarIconButton)d).Button.IsEnabled = (bool)e.NewValue;
}
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.RegisterAttached("Text", typeof(string), typeof(BindableApplicationBarIconButton), new PropertyMetadata(OnTextChanged));
public new static readonly DependencyProperty VisibilityProperty =
DependencyProperty.RegisterAttached("Visibility", typeof(Visibility), typeof(BindableApplicationBarIconButton), new PropertyMetadata(OnVisibilityChanged));
private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
var button = ((BindableApplicationBarIconButton)d);
BindableApplicationBar bar = button.Parent as BindableApplicationBar;
bar.Invalidate();
}
}
public new Visibility Visibility
{
get { return (Visibility)GetValue(VisibilityProperty); }
set { SetValue(VisibilityProperty, value); }
}
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != e.OldValue)
{
((BindableApplicationBarIconButton)d).Button.Text = e.NewValue.ToString();
}
}
public ApplicationBarIconButton Button { get; set; }
public BindableApplicationBarIconButton()
{
Button = new ApplicationBarIconButton();
Button.Text = "Text";
Button.Click += ApplicationBarIconButtonClick;
}
void ApplicationBarIconButtonClick(object sender, EventArgs e)
{
if (Command != null && CommandParameter != null)
Command.Execute(CommandParameter);
else if (Command != null)
Command.Execute(CommandParameterValue);
if (Click != null)
Click(this, e);
}
public bool IsEnabled
{
get { return (bool)GetValue(IsEnabledProperty); }
set { SetValue(IsEnabledProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public event EventHandler Click;
public Uri IconUri
{
get { return Button.IconUri; }
set { Button.IconUri = value; }
}
}
I have a text box for file name with path. After user locates a file using OpenFileDialog, this text box should be populated with filename. This text should also work when user enters filename with path directly instead of selecting from file dialog box.
Since I am learning MVVM, I am getting hard time to figure out how to update text box with filename/path. I tried everything i can think of.
I was expecting onPropertyChanged(“FilenameWithPath”) should take care this issue. Can somebody show me how to deal with this issue?
See code below
FileBrowseView.xaml
<TextBox Height="23" HorizontalAlignment="Left" Margin="113,22,0,0"
Name="txtFilenameWithPath"
Text="{Binding Path=FilenameWithPath,
UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
VerticalAlignment="Top" Width="300" />
<Button
Content="Browse..."
Height="30"
HorizontalAlignment="Left"
Margin="433,20,0,0"
Name="btnBrowse"
VerticalAlignment="Top"
Width="142"
Command="{Binding Path=BrowseCommand}" />
FileBrowseView.xaml.cs
public partial class FileBrowseView : Window
{
public FileBrowseView()
{
InitializeComponent();
DataContext = new FileBrowseViewModel();
}
}
FileBrowseModel
public class FileBrowseModel
{
private string _filenameWithPath = string.Empty;
public string FilenameWithPath
{
get { return _filenameWithPath; }
set
{
if (value == _filenameWithPath)
return;
else
_filenameWithPath = value;
}
}
}
FileBrowseViewModel
public class FileBrowseViewModel : INotifyPropertyChanged
{
private string _filenameWithPath = string.Empty;
public string FilenameWithPath
{
get { return _filenameWithPath; }
set
{
if (value == _filenameWithPath)
return;
else
_filenameWithPath = value;
OnPropertyChanged("FilenameWithPath");
}
}
private ICommand _browseCommand;
public ICommand BrowseCommand
{
get
{
if (_browseCommand == null)
_browseCommand = new DoBrowse();
return _browseCommand;
}
set
{
_browseCommand = value;
OnPropertyChanged("FilenameWithPath");
}
}
private class DoBrowse : ICommand
{
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
var filedialog = new System.Windows.Forms.OpenFileDialog();
DialogResult fresult = filedialog.ShowDialog();
if (fresult == System.Windows.Forms.DialogResult.OK)
{
FilenameWithPath = filedialog.FileName;
//I am trying to assign value i got from OpenFileDialog to
// FilenameWithPath property
//complier says "Cannot access non static member of outer type
'MyProject.FileBrowseViewModel' via
nested type 'MyProject.FileBrowseViewModel.DoBrowse
onPropertyChanged(“FilenameWithPath”);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You just need to set FileNameWithPath in your command's Execute function. And the setter for FileNameWithPath ought to be calling OnPropertyChanged. You shouldn't have to call that from your command's Execute function.
EDIT: Make sure that you are setting your data context to be the viewmodel and not the model since both have FilenameWithPath properties. If you were doing this the bind wouldn't fail because there is still a property to bind to. Otherwise:
Make the following changes:
public string FilenameWithPath
{
get { return _filenameWithPath; }
set
{
if (value == _filenameWithPath)
return;
else
{
_filenameWithPath = value;
OnPropertyChanged("FilenameWithPath");
}
}
}
and
if (fresult == System.Windows.Forms.DialogResult.OK)
{
FilenameWithPath = filedialog.FileName;
}
This should fix your problem. Additionally, consider changing which dialog box you use since this is WPF (as suggested in my comment).
Finally I am able to resolve this issue by adding new class called RelayCommand. I have modified the get block of _browseCommand use relay command as below.
private ICommand _browseCommand;
public ICommand BrowseCommand
{
get{
if (_browseCommand == null){
_browseCommand = new RelayCommand(
a => this.DoBrowseFolder(),
p => this.CheckCondition());
}
return _browseCommand;
}
set
{ _browseCommand = value;
OnPropertyChanged("FilenameWithPath");
}
}
public bool CheckCondition () {
//Check condition here if needed
return true;
}
private void DoBrowseFolder(){
var filedialog = new System.Windows.Forms.OpenFileDialog();
DialogResult fresult = filedialog.ShowDialog();
if (fresult == System.Windows.Forms.DialogResult.OK)
{
FilenameWithPath = filedialog.FileName;
OnPropertyChanged("FilenameWithPath ");
}
}
Relay Command Class
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
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]
public bool CanExecute(object parameter) {
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter) {
_execute(parameter);
}
#endregion // ICommand Members
}