I have a very simple solution below for tabs being populated based on MVVM. How do I setup the following two commands, and 'Add' and 'Remove'. From what I've read online it appears I need to setup ICommand or something along those lines. It wasn't clear enough to me in the demos for me to get it working.
The Add command would call the already existing function in the ViewModel class. Bu it would be called by a key command 'Ctrl + N'
The Remove command would be called when the user clicks the 'X' button, which would remove that particular tab. Otherwise it can be called by 'Ctrl + W' which would close whichever tab is currently selected.
The command stuff is new to me, so if someone could help me out it would be greatly appreciated. I hope to expand upon this and continue adding more to the tool.
Link to visual studio dropbox files. You'll see I've broken things out to classes and organized it in a way that makes things clear.
Snippets of tool below...
View Model
using System;
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication1
{
public class ViewModel : ObservableObject
{
private ObservableCollection<TabItem> tabItems;
public ObservableCollection<TabItem> TabItems
{
get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
}
public ViewModel()
{
TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
public void AddContentItem()
{
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
}
public class TabItem
{
public string Header { get; set; }
public string Content { get; set; }
}
}
MainWindow XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="250">
<Window.DataContext>
<data:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--<Button Content="Add" Command="{Binding AddCommand}" Grid.Row="0"></Button>-->
<TabControl ItemsSource="{Binding TabItems}" Grid.Row="1" Background="LightBlue">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
<Button Content="x" Width="20" Height="20" Margin="5 0 0 0"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
You've received two other answers already. Unfortunately, neither precisely addresses both the Add and Remove commands. Also, one prefers to focus primarily on code-behind implementation rather than XAML declarations, and is fairly sparse on details anyway, while the other more-correctly focuses on implementation in XAML where appropriate, but does not include correct, working code, and (slightly) obfuscates the answer by introducing the extra abstraction of the RelayCommand type.
So, I will offer my own take on the question, with the hopes this will be more useful to you.
While I agree that abstracting the ICommand implementation into a helper class such as RelayCommand is useful and even desirable, unfortunately this tends to hide the basic mechanisms of what's going on, and requires a more elaborate implementation that was offered in the other answer. So for now, let's ignore that.
Instead, just focus on what does need to be implemented: two different implementations of the ICommand interface. Your view model will expose these as the values of two bindable properties representing the commands to be executed.
Here is a new version of your ViewModel class (with the irrelevant and unprovided ObservableObject type removed):
class ViewModel
{
private class AddCommandObject : ICommand
{
private readonly ViewModel _target;
public AddCommandObject(ViewModel target)
{
_target = target;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_target.AddContentItem();
}
}
private class RemoveCommandObject : ICommand
{
private readonly ViewModel _target;
public RemoveCommandObject(ViewModel target)
{
_target = target;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_target.RemoveContentItem((TabItem)parameter);
}
}
private ObservableCollection<TabItem> tabItems;
public ObservableCollection<TabItem> TabItems
{
get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
}
public ICommand AddCommand { get { return _addCommand; } }
public ICommand RemoveCommand { get { return _removeCommand; } }
private readonly ICommand _addCommand;
private readonly ICommand _removeCommand;
public ViewModel()
{
TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
_addCommand = new AddCommandObject(this);
_removeCommand = new RemoveCommandObject(this);
}
public void AddContentItem()
{
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
public void RemoveContentItem(TabItem item)
{
TabItems.Remove(item);
}
}
Note the two added nested classes, AddCommandObject and RemoveCommandObject. These are both examples of nearly the simplest implementation of ICommand possible. They can always be executed, and so the return value of CanExecute() never changes (so there's no need to ever raise the CanExecuteChanged event). They do need the reference to your ViewModel object so that they can each call the appropriate method.
There are also two public properties added to allow binding of these commands. Of course, the RemoveContentItem() method needs to know what item to remove. This needs to be set up in the XAML, so that the value can be passed as a parameter to the command handler and from there to the actual RemoveContentItem() method.
In order to support the use of the keyboard for the commands, one approach is to add input bindings to the window. This is what I've chosen here. The RemoveCommand binding additionally needs the item to be deleted to be passed as the command parameter, so this is bound to the CommandParameter for the KeyBinding object (just as for the CommandParameter of the Button in the item).
The resulting XAML looks like this:
<Window.DataContext>
<data:ViewModel/>
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding AddCommand}">
<KeyBinding.Gesture>
<KeyGesture>Ctrl+N</KeyGesture>
</KeyBinding.Gesture>
</KeyBinding>
<KeyBinding Command="{Binding RemoveCommand}"
CommandParameter="{Binding SelectedItem, ElementName=tabControl1}">
<KeyBinding.Gesture>
<KeyGesture>Ctrl+W</KeyGesture>
</KeyBinding.Gesture>
</KeyBinding>
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TabControl x:Name="tabControl1" ItemsSource="{Binding TabItems}" Grid.Row="1" Background="LightBlue">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
<Button Content="x" Width="20" Height="20" Margin="5 0 0 0"
Command="{Binding DataContext.RemoveCommand, RelativeSource={RelativeSource AncestorType=TabControl}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}">
</Button>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
EDIT:
As I mentioned above, there is in fact benefit to abstracting the ICommand implementation, using a helper class instead of declaring a new class for every command you want to implement. The referenced answer at Why RelayCommand mentions loose coupling and unit testing as motivations. While I agree these are good goals, I can't say that these goals are in fact served per se by the abstraction of the ICommand implementation.
Rather, I see the benefits as being the same ones primarily found when making such abstractions: it allows for code reuse, and in doing so improves developer productivity, along with code maintainability and quality.
In my above example, every time you want a new command, you have to write a new class that implements ICommand. On the one hand, this means that each class you write can be tailor-made to the specific purpose. Dealing with CanExecuteChanged or not, as the case requires, passing parameters or not, etc.
On the other hand, every time you write such a class, that's an opportunity to write a new bug. Worse, if you introduce a bug which is then later copy/pasted, then when you eventually find the bug, you may or may not fix it everywhere it exists.
And of course, writing such classes over and over gets tedious and time-consuming.
Again, these are just specific examples of the general conventional wisdom of the "best practice" of abstracting reusable logic.
So, if we've accepted that an abstraction is useful here (I certainly have :) ), then the question becomes, what does that abstraction look like? There are a number of different ways to approach the question. The referenced answer is one example. Here is a slightly different approach that I've written:
class DelegateCommand<T> : ICommand
{
private readonly Func<T, bool> _canExecuteHandler;
private readonly Action<T> _executeHandler;
public DelegateCommand(Action<T> executeHandler)
: this(executeHandler, null) { }
public DelegateCommand(Action<T> executeHandler, Func<T, bool> canExecuteHandler)
{
_canExecuteHandler = canExecuteHandler;
_executeHandler = executeHandler;
}
public bool CanExecute(object parameter)
{
return _canExecuteHandler != null ? _canExecuteHandler((T)parameter) : true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_executeHandler((T)parameter);
}
public void RaiseCanExecuteChanged()
{
EventHandler handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
In the ViewModel class, the above would be used like this:
class ViewModel
{
private ObservableCollection<TabItem> tabItems;
public ObservableCollection<TabItem> TabItems
{
get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
}
public ICommand AddCommand { get { return _addCommand; } }
public ICommand RemoveCommand { get { return _removeCommand; } }
private readonly ICommand _addCommand;
private readonly ICommand _removeCommand;
public ViewModel()
{
TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
// Use a lambda delegate to map the required Action<T> delegate
// to the parameterless method call for AddContentItem()
_addCommand = new DelegateCommand<object>(o => this.AddContentItem());
// In this case, the target method takes a parameter, so we can just
// use the method directly.
_removeCommand = new DelegateCommand<TabItem>(RemoveContentItem);
}
Notes:
Of course, now the specific ICommand implementations are no longer needed. The AddCommandObject and RemoveCommandObject classes have been removed from the ViewModel class.
In their place, the code uses the DelegateCommand<T> class.
Note that in some cases, the command handler is not going to need the parameter passed to the ICommand.Execute(object) method. In the above, this is addressed by accepting the parameter in a lambda (anonymous) delegate, and then ignoring it while calling the parameterless handler method. Other ways to approach this would be to have the handler method accept the parameter but then ignore it, or to have a non-generic class in which the handler delegate itself can be parameterless. IMHO, there's no "right way" per se…just various choices that may be considered more or less preferable according to personal preference.
Note also that this implementation differs from the referenced answer's implementation in the handling of the CanExecuteChanged event. In my implementation, the client code is given fine-grained control over that event, at the expense of requiring the client code to retain a reference to the DelegateCommand<T> object in question and calling its RaiseCanExecuteChanged() method at the appropriate time. In the other implementation, it instead relies on the CommandManager.RequerySuggested event. This is a trade-off of convenience vs efficiency and, in some cases, correctness. That is, it is less convenient for the client code to have to retain references to commands which may change the executable status, but if one goes the other route, at the very least the CanExecuteChanged event may be raised much more often than is required, and in some cases it's even possible it may not be raised when it should have been (which is far worse than the possible inefficiency).
On that last point, yet another approach would be to make the ICommand implementation a dependency object, and provide a dependency property that is used to control the executable state of the command. This is a lot more complicated, but overall could be considered the superior solution as it allows fine-grained control over the CanExecuteChanged event's raising, while providing a good, idiomatic way to bind the executable state of the command, e.g. in XAML to whatever property or properties actually determine said executability.
Such an implementation might look something like this:
class DelegateDependencyCommand<T> : DependencyObject, ICommand
{
public static readonly DependencyProperty IsExecutableProperty = DependencyProperty.Register(
"IsExecutable", typeof(bool), typeof(DelegateCommand<T>), new PropertyMetadata(true, OnIsExecutableChanged));
public bool IsExecutable
{
get { return (bool)GetValue(IsExecutableProperty); }
set { SetValue(IsExecutableProperty, value); }
}
private static void OnIsExecutableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DelegateDependencyCommand<T> command = (DelegateDependencyCommand<T>)d;
EventHandler handler = command.CanExecuteChanged;
if (handler != null)
{
handler(command, EventArgs.Empty);
}
}
private readonly Action<T> _executeHandler;
public DelegateDependencyCommand(Action<T> executeHandler)
{
_executeHandler = executeHandler;
}
public bool CanExecute(object parameter)
{
return IsExecutable;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_executeHandler((T)parameter);
}
}
In the above, the canExecuteHandler argument for the class is eliminated, in lieu of the IsExecutable property. When that property changes, the CanExecuteChanged event is raised. IMHO it's unfortunate that there is this discrepancy in the ICommand interface between how it's designed and how WPF normally works (i.e. with bindable properties). It's a bit weird that we have essentially a property but which is exposed via an explicit getter method named CanExecute().
On the other hand, this discrepancy does serve some useful purposes, including making explicit and convenient the use of the CommandParameter for both the execution of the command, and the checking for executability. These are worthwhile goals. I'm just not sure personally whether I'd have made the same choice balancing them with the consistency of the usual way state is connected within WPF (i.e. through binding). Fortunately, it's simple enough to implement the ICommand interface in a bindable way (i.e. as above), if that's really desired.
You can start with remove.
First, you will need to create a RelayCommand class. More info about RelayCommand, see this post: Why RelayCommand
public class RelayCommand : ICommand
{
#region Private members
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
private readonly Action execute;
/// <summary>
/// True if command is executing, false otherwise
/// </summary>
private readonly Func<bool> canExecute;
#endregion
/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/> that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute): this(execute, canExecute: null)
{
}
/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/>.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this.execute = execute;
this.canExecute = canExecute;
}
/// <summary>
/// Raised when RaiseCanExecuteChanged is called.
/// </summary>
public event EventHandler CanExecuteChanged;
/// <summary>
/// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
/// <returns>True if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter)
{
return this.canExecute == null ? true : this.canExecute();
}
/// <summary>
/// Executes the <see cref="RelayCommand"/> on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
public void Execute(object parameter)
{
this.execute();
}
/// <summary>
/// Method used to raise the <see cref="CanExecuteChanged"/> event
/// to indicate that the return value of the <see cref="CanExecute"/>
/// method has changed.
/// </summary>
public void RaiseCanExecuteChanged()
{
var handler = this.CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
Next, add a "bindable" Remove property in your ViewModel of type RelayCommand. This is what your TabItem buttons will bind to, and how it will notify the ViewModel it was pressed. The ReplayCommand will have an execute method that will get called whenever a press occurs. Here, we remove our-self from the overall TabItem list.
public class ViewModel : ObservableObject
{
private ObservableCollection<TabItem> tabItems;
private RelayCommand<object> RemoveCommand;
public ObservableCollection<TabItem> TabItems
{
get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
}
public ViewModel()
{
TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
RemoveCommand = new RelayCommand<object>(RemoveItemExecute);
}
public void AddContentItem()
{
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
private void RemoveItemExecute(object param)
{
var tabItem = param as TabItem;
if (tabItem != null)
{
TabItems.Remove(tabItem);
}
}
}
Now, update your XAML. Each TabItem will need to bind to the RemoveCommand in the parent ViewModel and pass itself in as a parameter. We can do this like:
<!--<Button Content="Add" Command="{Binding AddCommand}" Grid.Row="0"></Button>-->
<TabControl x:Name="TabItems" ItemsSource="{Binding TabItems}" Grid.Row="1" Background="LightBlue">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
<Button Command="{Binding ElementName=TabItems, Path=DataContext.RemoveCommand}"
CommandParameter="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}"
Content="x"
Width="20"
Height="20"
Margin="5 0 0 0"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
At first u need to setup your commands
public static class Commands
{
private static RoutedUICommand add;
private static RoutedUICommand remove;
static Commands()
{
searchValue = new RoutedUICommand("Add", "Add", typeof(Commands));
showCSCode = new RoutedUICommand("Remove", "Remove", typeof(Commands));
add.InputGestures.Add(new KeyGesture(Key.N, ModifierKeys.Control));
remove.InputGestures.Add(new KeyGesture(Key.X));
}
public static RoutedUICommand Add { get { return add; } }
public static RoutedUICommand Remove { get { return remove; } }
}
In window loaded event you have to bind the command methods
<window ... Loaded="window_loaded">
The cs file
CommandBindings.Add(new CommandBinding(Commands.Remove, HandleRemoveExecuted, HandleCanRemoveExecuted));
Is command enabled:
private void HandleCanAddExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
What should the command do:
private void HandleAddExecute(object sender, ExecutedRoutedEventArgs e)
{
AddContentItem();
}
At last you just have to edit your existing files with
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString(),
CommandBindings.Add(new CommandBinding(Commands.Add, HandleAddExecuted, HandleCanAddExecuted)); });
xaml:
<Window ...
xmlns:commands="clr-namespace:<NAMESPACE>">
<Button Content="x" Width="20"
Height="20" Margin="5 0 0 0"
Command="{x:Static commands:Commands.Remove}"/>
Related
Edit:
Ok after finally playing around numerous times without no luck, I have created a very small Wpf application. You can directly copy this code. Notice when you change values in the TextBox and press the Test button, the values never get updated. I don't understand why the two way binding dosen't work. Please help.
Here is the xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemsSource="{Binding Path=Demo.CurrentParameterValue,Mode=TwoWay}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=.,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="100"></TextBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Row="1" Click="Button_Click">TEST</Button>
</Grid>
Here is the xaml.cs:
namespace WpfApp9
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private VmServiceMethodsViewDataGridModel _demo;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public VmServiceMethodsViewDataGridModel Demo
{
get => _demo;
set
{
_demo = value;
OnPropertyChanged("Demo");
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Demo = new VmServiceMethodsViewDataGridModel();
Demo.CurrentParameterValue.Add(1);
Demo.CurrentParameterValue.Add(2);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var collection = Demo.CurrentParameterValue;
MessageBox.Show(string.Format("Values are {0}, {1}", collection[0], collection[1]));
}
}
public class VmServiceMethodsViewDataGridModel : INotifyPropertyChanged
{
private List<object> _currentParameterValue;
public List<object> CurrentParameterValue
{
get => _currentParameterValue;
set
{
_currentParameterValue = value;
OnPropertyChanged("CurrentParameterValue");
}
}
public VmServiceMethodsViewDataGridModel()
{
CurrentParameterValue = new List<object>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
The problem with your binding is that you are trying to bind to an object. This is perfectly fine in a OneWay/OneTime scenario. But not when using binding TwoWay. You can change the value of a property e.g. in your view model, but you can't change the object instance itself. In your specific case, the binding would have to send the new long input to the view model's value collection and replace the old value. Of course this will never happen as Binding is not designed to work this way.
The technical reason is that changing the instance would mean to change the Binding.Source. Once the binding is active (controlled by a BindingExpression) it becomes immutable. Changing the source is not allowed. That's also the reason why {Binding Source={DynamicResource ...}} won't work. The BindingSource can only be static (or StaticResource - not changing resource).
You usually bind to properties. In a TwoWay binding scenario Binding can simply update the property's value. So the solution to your problem is to wrap the long values into a class and bind the TextBox to a property of this class to retrieve/modify the actual value.
In this context your code looks too complicated.
Your object structure is too complex or unnatural.
You don't need to apply the DataTemplate to a ContentControl (in XAML).
And of course as this is a UWP application, use x:Bind where possible as it will improve performance. The converter is redundant as Binding and x:Bind allow a nested PropertyPath e.g.
<ListView ItemsSource="{Binding CurrentParameterValue.ListParameterValues}">
ItemsControl.ItemsSource doesn't need a TwoWay binding. The ItemsControl will never update/replace the source collection. If you don plan to replace the source collection in the view model (e.g., AtlasMethodParameterList = new ObservableCollection<>()), then you can even set the binding mode to OneTime (which would be the default for x:Bind).
I recommend to use OneTime and if you need to replace the collection, rather call Clear() on the collection and add the new items. This will improve the performance.
Never use async void in a method signature except for event handlers.
Always use async Task, when the return type is void or when returning a value async Task<TResult>. Otherwise you will experience unexpected side effects, especially when encountering exceptions:
// An async void method must return Task
private async Task GetParameterList(string obj)
Also async methods should always be awaited. This means the method calling and awaiting an async method must itself return Task or Task<T> to be awaitable. A method returning type void cannot be awaited.
All DependencyProperty of every control, have their Binding.UpdateSourceTrigger set to UpdateSourceTrigger.PropertyChanged by default.
Exceptions are properties that are likely to raise too much consecutive property changes like a TextBox would do on each input/key press. TextBox.Text has the default set to UpdateSourceTrigger.LostFocus.
You should remove all redundant UpdateSourceTrigger.PropertyChanged from the bindings to improve readability.
Consider to use out instead of ref if you don't intend to read the variable. If you only set the value prefer to use out to hint your intent to any reader. Use in if don't intent to modify the reference (read-only reference).
Your Set method should look something like this:
protected virtual void Set<TValue>(out TValue valueTarget, TValue value, [CallerMemberName] string propertyName = null)
{
if (value != valueTarget)
{
valueTarget = value;
OnPropertyChanged(propertyName);
}
}
I refactored your complete code trying to improve it:
Parameter.cs
// The type that wraps the actual parameter value.
// Consider to use dedicated types e.g., LongParameter instead, to allow a strongly typed Value property instead of a basic property of type object.
// This prevents implicit boxing/unboxing in order to convert from object/reference type to primitive/value type and vice versa. This will improve performance.
// (Only needed because we are dealing with primitive/value types like long, double, etc)
// You would then have to define a DataTemplate for each type. Don't forget to set x:DataType on each DataTemplate.
public class Parameter : BindableBase
{
protected Parameter(object value)
{
this.Value = value;
}
private object value;
public object Value
{
get => this.value;
set => Set(out this.value, value);
}
}
VmServiceModel.cs
public class VmServiceModel : BindableBase
{
public VmServiceModel()
{
this.Parameters = new List<Parameter>();
}
private List<Parameter> _parameters;
public List<Parameter> Parameters
{
get => this._parameters;
set => Set(out this._parameters, value);
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.AtlasMethodParameterList = new ObservableCollection<VmServiceModel>();
}
private ObservableCollection<VmServiceModel> _atlasMethodParameterList;
public ObservableCollection<VmServiceModel> AtlasMethodParameterList
{
get => _atlasMethodParameterList;
set => Set(out _atlasMethodParameterList, value);
}
private async Task GetParameterList(string obj)
{
foreach (var item in this.ParametersCollection)
{
var vmServiceModel = new VmServiceModel();
vmServiceModel.Parameters
.AddRange(item.Value.Cast<long>().Select(innerItem => new Parameter(innerItem)));
this.AtlasMethodParameterList.Add(vmServiceModel);
}
}
}
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public ViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new ViewModel();
}
}
MainPage.xaml
<Page>
<Page.Resources>
<DataTemplate x:Key="ListIntTemplate" x:DataType="local:VmServiceModel">
<ListView ItemsSource="{x:Bind Parameters}"
HorizontalAlignment="Center"
SelectionMode="None" Background="Transparent">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Parameter">
<TextBox Text="{Binding Value Mode=TwoWay}" Height="36" Width="65"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</Page.Resources>
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.AtlasMethodParameterList}"
ItemTemplate="{StaticResource ListIntTemplate}">
</ListView>
</Grid>
</Page>
But when I change the values in the TextBox it dosen't update back the source that is the CurrentParameterValue property.
Binding in ListView doesn't know how to update the Property of type object because it's ItemsSource and it can update only ICollection such as you can't interact with object like List in C#. for example:
object MyList = new object();
MyList.Add("something"); // Compile error
And in my viewmodel the object which can be a list of long, list of double etc comes from an external API.
You need this solution then.
public class VmServiceMethodsViewDataGridModel : BindableBaseThreadSafe
{
private List<object> _currentParameterValue; // or ObservableCollection
public List<object> CurrentParameterValue
{
get => _currentParameterValue;
set => Set(ref _currentParameterValue, value);
}
}
Additionally
I have no idea what do you want to achieve or solve with this syntax
<ListView ItemsSource="{x:Bind ViewModel.AtlasMethodParameterList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
Everything must work with this
<ListView ItemsSource="{Binding AtlasMethodParameterList}">
Mode=TwoWay is default Mode, you may not include it here explicitly.
UpdateSourceTrigger=PropertyChanged (Default is LostFocus) is needed in UI->VM direction, not in a back way. So, it's useless here. You may apply it to the TextBox in template instead.
EDIT
Because Two-way Binding requires explicit Path and the target must be a Property which contains Setter.
The workaround with your Demo app
<ListView Grid.Row="0"
ItemsSource="{Binding Demo.CurrentParameterValue}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" Width="100"></TextBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private VmServiceMethodsViewDataGridModel _demo;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public VmServiceMethodsViewDataGridModel Demo
{
get => _demo;
set
{
_demo = value;
OnPropertyChanged("Demo");
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Demo = new VmServiceMethodsViewDataGridModel();
Demo.CurrentParameterValue.Add(new MyItem { Value = 1 });
Demo.CurrentParameterValue.Add(new MyItem { Value = 2 });
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var collection = Demo.CurrentParameterValue;
MessageBox.Show(string.Format("Values are {0}, {1}", collection[0].Value, collection[1].Value));
}
}
// here it is
public class MyItem
{
public object Value { get; set; }
}
public class VmServiceMethodsViewDataGridModel : INotifyPropertyChanged
{
private List<MyItem> _currentParameterValue;
public List<MyItem> CurrentParameterValue
{
get => _currentParameterValue;
set
{
_currentParameterValue = value;
OnPropertyChanged("CurrentParameterValue");
}
}
public VmServiceMethodsViewDataGridModel()
{
CurrentParameterValue = new List<MyItem>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Additionally you may implement INPC for the Value regarding to your needs.
I'm learning WPF.
In one of the exercises, I have a TextBox and buttons Cut and Paste. The following is enough to implement Cut and Paste functionality:
XAML:
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Margin="3">
<Button Command="ApplicationCommands.Cut"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60">
_Cut
</Button>
<Button Command="ApplicationCommands.Paste"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60" Margin="3,0">
_Paste<
/Button>
</WrapPanel>
<TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>
When pressed, the button Cut executes the ApplicationCommands.Cut on the TextBox with name txtEditor. When needed, the button will ask the TextBox with name textEditor if it can execute a Cut command, and when pressed it will order the textEditor to execute the Cut command.
Fairly straightforward. It works fine.
Just for Fun, I'd like to implement another button: Clear. When pressed it should clear the TextBox. The Textbox class has a method Clear.
<Button Command="ApplicationCommands.Clear"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60">
Clear
</Button>
Alas, this won't work. ApplicationCommands doesn't have a Clear. Should I implement a custom command, as suggested in this example?
I tried the following:
I implemented CanExecute and Executed methods in my window:
public partial class CustomCommandSample : Window
{
public CustomCommandSample()
{
InitializeComponent();
}
private void ClearCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
private void ClearCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
txtEditor.Clear();
}
}
A static CustomCommands class:
public static class CustomCommands
{
public static RoutedUICommand Clear => new RoutedUICommand (
"Clear",
"Clear",
typeof(CustomCommands));
}
Finally the XAML:
(Note: the classes in this project are in namespace WpfCommandDemo. Xaml refers to it as Local)
<Window x:Class="WpfTutorialSamples.Commands.UsingCommandsSample"
xmlns="...
xmlns:local="clr-namespace:WpfCommandDemo"
Title="UsingCommandsSample" Height="100" Width="200">
<Window.CommandBindings>
<CommandBinding Command="CustomCommands.Clear"
CanExecute="ClearCommand_CanExecute"
Executed="ClearCommand_Executed" />
</Window.CommandBindings>
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Margin="3">
<Button Command="CustomCommands.Clear"
CommandTarget="{Binding ElementName=txtEditor}"
Width="60">
Clear
</Button>
... (other buttons: cut / paste, as above
</WrapPanel>
<TextBox AcceptsReturn="True" Name="txtEditor" />
</DockPanel>
Although this compiles, The constructor of CustomCommandSample throws an XamlParseException:
Type reference cannot find type named
'{http://schemas.microsoft.com/winfx/2006/xaml/presentation}CustomCommands'.
Should I solve the problem using Custom Commands? What should I change? Or am I completely wrong, and should I solve this differently
To use CustomCommands in XAML, you'll need to add a reference to it. In the element, add a line:
xmlns:custom="clr-namespace:MyApplication.NamespaceWithCustomInIt"
Replacing the namespace value as appropriate. Then you should be able to reference CustomCommands anywhere in XAML as custom:CustomCommands (may have to bind, I'll check later).
Should I solve the problem using Custom Commands?
Yes. This is how to solve this using the Model-View-ViewModel (MVVM) design pattern which is the recommended design pattern to use when developing XAML based UI applications.
From this blog post:
WPF provides two implementations of the ICommand interface; the System.Windows.Input.RoutedCommand and System.Windows.Input.RoutedUICommand where the latter is a subclass of the former that simply adds a Text property that describes the command. However, neither of these implementations are especially suited to be used in a view model as they search the visual tree from the focused element and up for an element that has a matching System.Windows.Input.CommandBinding object in its CommandBindings collection and then executes the Execute delegate for this particular CommandBinding. Since the command logic should reside in the view model, you don’t want to setup a CommandBinding in the view in order to connect the command to a visual element. Instead, you can create your own command by creating a class that implements the ICommand. The below implementation is a common one that invokes delegates for the Execute and CanExecute methods:
public class DelegateCommand: System.Windows.Input.ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public DelegateCommand(Action<object> execute)
: this(execute, null) { }
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
Once you have an implementation of the ICommand interface, it's easy to use in your view models:
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
ClearCommand = new DelegateCommand(Clear);
}
private string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged(); }
}
public ICommand ClearCommand { get; }
private void Clear(object parameter)
{
Text = string.Empty;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
In the view, you simply bind to the properties of the view model:
<TextBox AcceptsReturn="True" Name="txtEditor" Text="{Binding Text}" />
<Button Content="Clear" Command="{Binding ClearCommand}" />
Just remember to set the DataContext of the view to an instance of your view model for the bindings to work:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
How to write WPF OpenFileDialog using MVVM (Model-View-ViewModel) in c#?
I have visited some websites regarding this OpenFileDialog ,but I didn't get clear idea about it. This is my code
In View.xaml:
<Window.... xmlns:VM="clr-namespace:myproject.myViewModel"
... >
<Window.DataContext><VM:myViewModel/>
</Window.DataContext>
<ItemsControl ItemsSource="{Binding mygroup}" >
<ItemsControl.ItemTemplate >
<DataTemplate>
<Grid >....
<TextBlock Text="Color" Margin="20" VerticalAlignment="Center"/>
<ComboBox KeyboardNavigation.TabIndex="0" Grid.Column="1" Margin="45,10,10,10" Height="30" Width="200" ItemsSource="{Binding Color}" />
<TextBlock Text="Shapes" Grid.Row="1" VerticalAlignment="Center" />
<ComboBox KeyboardNavigation.TabIndex="3" Grid.Column="1" Grid.Row="1" Height="20" Width="150" SelectedIndex="0" HorizontalContentAlignment="Right"
VerticalAlignment="Center" ItemsSource="{Binding Shapes}">
</ComboBox>
<TextBlock Text="Open Files " VerticalAlignment="Center" Grid.Row="0" Grid.Column="2" Margin="10" />
<TextBox Grid.Column="3" Text="" Height="30" Grid.Row="0" IsReadOnly="True"
TextAlignment="Right" VerticalContentAlignment="Center" Width="200" /> <Button Grid.Column="4" Content="Browse" Height="30" VerticalAlignment="Bottom" MinWidth="41" />
</Grid>
</Window>
In Model.cs:
namespace Myproject.Models
{
public class ProjectModel : INotifyPropertyChanged
{
private ObservableCollection<string> color;
private ObservableCollection<string> shapes;
public ObservableCollection<string> Color
{
get { return color; }
set
{
color = value;
NotifyPropertyChanged("Color");
}
}
public ObservableCollection<string> Shapes
{
get { return shapes; }
set
{
shapes = value;
NotifyPropertyChanged("Shapes");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Private Helpers
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
In ViewModel.cs:
namespace MyProject.ViewModels
{
public class myProjectViewModel : INotifyPropertyChanged
{
private ObservableCollection<myprojectmodel> mygroup;
public ObservableCollection<myprojectmodel> Mygroup
{
get => this.mygroup;
set
{
if (Equals(value, this.mygroup)) return;
this.mygroup = value;
OnPropertyChanged();
}
}
public ProjectViewModel()
{
Mygroup = new ObservableCollection<myprojectmodel>();
List<string> lstColor = new List<string>();
lstCity = new List<string> {"Pink", "Blue", "Red"};
List<string> lstShapes = new List<string>();
lstTemperature = new List<string> {"Rectangle", "Triangle", "Circle"};
Mygroup.Add(
new ProjectModel
{
Color= new ObservableCollection<string>(lstColor),
Shapes = new ObservableCollection<string>(lstShapes),
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
How should I write code to get OpenFileDialog.
I have seen this link WPF OpenFileDialog with the MVVM pattern? but I don't know how to write it for my above code.
please someone help me by editing my code to get OpenFileDailog.
In my opinion this kind of things doesn't belong in to the ViewModel. It's View specific logic. The View alone handles user input and then sends it to the ViewModel. ViewModel never asks the View to do something. This would invert the dependency chain and couple the ViewModel to the View. The dependencies have to be like this:
View --> ViewModel --> Model. The ViewModel doesn't even know about the type of views nor that there is a View at all.
You have to open the dialog from your view and then send the result to the view model.
For this you could create a simple event handler in your code-behind and attach it to a button's click event. You take the picked file and use an ICommand to invoke e.g. an open file action. That's MVVM (or MVP). Separate the concerns of the views from your models.
MainWindow.xaml:
<Window x:Class="WpfOpenDialogExample.OpenFileDialogSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="OpenFileDialogSample" Height="300" Width="300">
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<Grid>
<Button Name="ShowFilePickerButton" Click="ShowFilePicker_OnClick" Content="Open file" />
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.IO;
using System.Windows;
using Microsoft.Win32;
namespace WpfOpenDialogExample
{
public partial class OpenFileDialogSample : Window
{
public OpenFileDialogSample()
{
InitializeComponent();
}
private void ShowFilePicker_OnClick(object sender, RoutedEventArgs e)
{
var viewModel = this.DataContext as ViewModel;
OpenFileDialog openFileDialog = new OpenFileDialog();
if(openFileDialog.ShowDialog() == true && viewModel.OpenFileCommand.CanExecute(openFileDialog.FileName))
{
viewModel.OpenFileCommand.Execute(openFileDialog.FileName);
}
}
private void ShowFolderPicker_OnClick(object sender, RoutedEventArgs e)
{
var viewModel = this.DataContext as ViewModel;
FolderBrowserDialog openFolderDialog = new FolderBrowserDialog();
if(openFolderDialog.ShowDialog() == DialogResul.Ok && viewModel.OpenFolderCommand.CanExecute(openFolderDialog.SelectedPath ))
{
viewModel.OpenFolderCommand.Execute(openFolderDialog.SelectedPath );
}
}
}
}
ViewModel.cs:
public ICommand OpenFileCommand { get => new RelayCommand(OpenFile, CanOpenFile); }
private void OpenFile(string filePath)
{
...
}
private bool CanOpenFile(string filePath)
{
return File.Exists(filePath);
}
public ICommand OpenFolderCommand { get => new RelayCommand(OpenFolder, CanOpenFolder); }
private void OpenFolder(string folderPath)
{
...
}
private bool CanOpenFolder(string folderPath)
{
return Directory.Exists(filePath);
}
RelayCommand.cs:
using System;
using System.Windows.Input;
namespace WpfOpenDialogExample
{
/// <summary>
/// An implementation independent ICommand implementation.
/// Enables instant creation of an ICommand without implementing the ICommand interface for each command.
/// The individual Execute() an CanExecute() members are suplied via delegates.
/// <seealso cref="System.Windows.Input.ICommand"/>
/// </summary>
/// <remarks>The type of <c>RelaisCommand</c> actually is a <see cref="System.Windows.Input.ICommand"/></remarks>
public class RelayCommand : ICommand
{
/// <summary>
/// Default constructor to declare the concrete implementation of Execute(object):void and CanExecute(object) : bool
/// </summary>
/// <param name="executeDelegate">Delegate referencing the execution context method.
/// Delegate signature: delegate(object):void</param>
/// <param name="canExecuteDelegate">Delegate referencing the canExecute context method.
/// Delegate signature: delegate(object):bool</param>
public RelayCommand(Action<object> executeDelegate , Predicate<object> canExecuteDelegate)
{
this.executeDelegate = executeDelegate;
this.canExecuteDelegate = canExecuteDelegate;
}
/// <summary>
/// Invokes the custom <c>canExecuteDelegate</c> which should check wether the command can be executed.
/// </summary>
/// <param name="parameter">Optional parameter of type <see cref="System.Object"/></param>
/// <returns>Expected to return tue, when the preconditions meet the requirements and therefore the command stored in <c>executeDelegate</c> can execute.
/// Expected to return fals when command execution is not possible.</returns>
public bool CanExecute(object parameter)
{
if (this.canExecuteDelegate != null)
{
return this.canExecuteDelegate(parameter);
}
return false;
}
/// <summary>
/// Invokes the custom <c>executeDelegate</c>, which references the command to execute.
/// </summary>
/// <param name="parameter">Optional parameter of type <see cref="System.Object"/></param>
public void Execute(object parameter)
{
if (this.executeDelegate != null)
this.executeDelegate(parameter);
}
/// <summary>
/// The event is triggered every time the conditions regarding the command have changed. This occures when <c>InvalidateRequerySuggested()</c> gets explicitly or implicitly called.
/// Triggering this event usually results in an invokation of <c>CanExecute(object):bool</c> to check if the occured change has made command execution possible.
/// The <see cref="System.Windows.Input.CommandManager"/> holds a weakrefernce to the observer.
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private readonly Action<object> executeDelegate;
private readonly Predicate<object> canExecuteDelegate;
}
}
In this code:
<ListBox
x:Name="DataList1"
xmlns:local="clr-namespace:xaml_binding_commands"
>
<ListBox.Resources>
<local:CommandUp x:Key="CommandUp1"></local:CommandUp>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox
Text="{Binding Height,Mode=TwoWay,StringFormat=00\{0:d\}}"
InputScope="Number"/>
<RepeatButton
Content="+"
Command="{StaticResource CommandUp1}"
CommandParameter="{Binding }"
/>
<TextBox
Text="{Binding Weight,Mode=TwoWay}"
InputScope="Number"/>
<RepeatButton
Content="+"
Command="{StaticResource CommandUp1}"
CommandParameter="{Binding Weight}"
/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And this
namespace xaml_binding_commands
{
public class CommandUp : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (parameter is Entry)
{
Entry EntryToUp = (Entry)parameter;
EntryToUp.Height +=1; // no worky, which field to increment?!
}
if (parameter is Int32)
{
Int32 EntryFieldToUp = (Int32)parameter;
EntryFieldToUp += 1; // no worky
}
}
}
public class Entry : INotifyPropertyChanged
{
private Int32 _Height;
public Int32 Height
{
get { return _Height; }
set { _Height = value; PropChange("Height"); }
}
private Int32 _Weight;
public Int32 Weight
{
get { return _Weight; }
set { _Weight = value; PropChange("Weight"); }
}
private void PropChange(String PropName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(PropName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
this.Loaded += MainPage_Loaded;
}
private ObservableCollection<Entry> _People = new ObservableCollection<Entry>();
public ObservableCollection<Entry> People
{
get { return _People; }
set { _People = value; }
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
DataList1.ItemsSource = People;
People.Add( new Entry() { Height=67, Weight=118 } );
}
}
}
Can I pass the field that the textbox is bound to by reference? If I pass the entire class, an Entry, to the CommandParameter so it can operate and increment, I have no way of knowing which set of TextBoxes and Buttons caused the Command.Execute. If I pass the same thing that the TextBox is bound to, namely, individually: Weight,Height then the Command.Execute has no way of affecting the input. Usually I would PassByReference and my Int32 would be boxed, and my general operate function could operate on the by-ref parameter. Can I do this somehow in XAML?
If I was doing this, I would use MVVM Light and RelayCommand<string>. This means that you can pass in one (or more) parameters in as part of the binding to the ICommand.
This means that you could have multiple bindings to a single event handler attached to a button, and each binding could have a different parameter that let you know where it came from.
Update 1
MVVM Light is an MVVM library that is compatible with pretty much everything, from standard WPF to Windows 8.1 to Windows phone. See http://www.mvvmlight.net/. It is probably the most popular MVVM lib according to NuGet download stats, and the one that I tend to prefer.
For an example of how to use MVVM light with a CommandParameter, see the top voted answer at MVVM Light RelayCommand Parameters.
For an example of how to pass in two or more parameters to a RelayCommand, see How to Passing multiple parameters RelayCommand?
Update 2
Just looking at your code, I would use MVVM. I generally prefer MVVM to code behind (this is a whole discussion in itself). If you put all of your data in the ViewModel, and used bindings to let your XAML View update the ViewModel, I think things would become a lot easier to develop and maintain.
In my View, I try to tie an event to the Enter key by the following XAML:
<TextBox x:Name="txtFields" Text="{Binding FieldsTextProperty, UpdateSourceTrigger=PropertyChanged}" Height="23" TextWrapping="NoWrap" Background="#FFCBEECD" AcceptsReturn="False" >
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding AddFieldCommand}"></KeyBinding>
</TextBox.InputBindings>
</TextBox>
The AddFieldCommand exists in my ViewModel as a property:
public ICommand AddFieldCommand { get; private set; }
In the ViewModel Constructor the following RelayCommand exists.
AddFieldCommand = new RelayCommand(AddField);
And from the RelayCommand the method AddField is called.
public void AddField()
{
Console.WriteLine("AddField Method")
}
This doesn't work - the AddField method is never called. Can anybody help?
I wonder if the the .InputBindings aren't working in this scenario. Keyboard input handling is probably being hijacked by the TextBox.
Assuming you want to stick to the MVVM pattern and avoid event-handling code in the code-behind, I'd probably choose to create a custom implementation of the TextBox - call it a 'SubmitTextBox'
The custom SubmitTextBox could automatically hook up to the PreviewKeyDown event, and monitor the Enter key.
You could further adhere to MVVM by adding an ICommand DP to handle the 'Submit' event.
Something like this ...
public class SubmitTextBox : TextBox
{
public SubmitTextBox()
: base()
{
PreviewKeyDown += SubmitTextBox_PreviewKeyDown;
}
private void SubmitTextBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Enter)
{
if (this.SubmitCommand != null && this.SubmitCommand.CanExecute(this.Text))
{
// Note this executes the command, and returns
// the current value of the textbox.
this.SubmitCommand.Execute(this.Text);
}
}
}
/// <summary>
/// The command to execute when the text is submitted (Enter is pressed).
/// </summary>
public ICommand SubmitCommand
{
get { return (ICommand)GetValue(SubmitCommandProperty); }
set { SetValue(SubmitCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for SubmitCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SubmitCommandProperty =
DependencyProperty.Register("SubmitCommand", typeof(ICommand), typeof(SubmitTextBox), new PropertyMetadata(null));
}
And your XAML would end up looking like this:
<custom:SubmitTextBox
x:Name="txtFields"
Text="{Binding FieldsTextProperty}"
SubmitCommand="{Binding AddFieldCommand}"
Height="23"
TextWrapping="NoWrap"
Background="#FFCBEECD" />
Hope that helps :)
UPDATE: To clarify, the SubmitCommand I created returns the current text in the textbox as a parameter. In order to use this with the MVVM-Light toolkit, you'll need to create a RelayCommand that can accept the type 'string'.
public RelayCommand<string> AddFieldCommand { get; private set; }
public ViewModelConstructor()
{
AddFieldCommand = new RelayCommand<string>(AddField);
}
private void AddField(string text)
{
// Do something
}
I hope that clears things up a litte :)