Accessing UserControls in a ViewModel - wpf

The View : I have UserControl, which has a TextBox and a Label. When the "Enter" key is down, I want the Label to be updated with the value form the text box. For the sake of this example, I created a CarUserControl. I will be hosting a list of these in an ItemsControl in the MainWindow.
The Model : I have class Car, which will be the model.
The ViewModel : I do not have a ViewModel for the CarUserControl and the Car. I have one for the MainWindow instead - lets call this the MainViewModel.
I can get the commands propogated from the individual usercontrols to the MainViewModel, but I'm unsure about getting the values from the textboxes in the MainViewModel?
Here are some of the assumptions I'm making from what I`ve read online about MVVM (there are ofcourse some sources which say the assumptions are wrong).
1] Usercontrols should not have a ViewModel.
2] Usercontrols should only expose dependency properties, and not public properties with INotifyChanged or events.
So, the question is, how do I update the label, and access the TextBox value in the MainViewModel.
Here is the test code :
-----CarUserControl.xaml----
<UserControl x:Class="TestMVVM.CarUserControl"
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:local="clr-namespace:TestMVVM"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="300" x:Name="thisUC">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0">--</Label>
<TextBox Grid.Column="1" Background="#FFE8D3D3" BorderThickness="0">
<TextBox.InputBindings>
<KeyBinding Key="Enter"
Command="{Binding KeyDownCommand, ElementName=thisUC}"
CommandParameter="{Binding}"/>
</TextBox.InputBindings>
</TextBox>
</Grid>
</UserControl>
-----CarUserControl.cs-----
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace TestMVVM
{
/// <summary>
/// Interaction logic for CarUserControl.xaml
/// The Usercontrol
/// </summary>
public partial class CarUserControl : UserControl
{
private static readonly DependencyProperty StrValueProperty = DependencyProperty.Register("StrValue", typeof(float), typeof(CarUserControl), new PropertyMetadata(null));
private static readonly DependencyProperty KeyDownCommandProperty = DependencyProperty.Register("KeyDownCommand", typeof(ICommand), typeof(CarUserControl), new PropertyMetadata(null)); //Enter key down in the text box
public CarUserControl()
{
InitializeComponent();
}
public string StrValue
{
get { return (string)GetValue(StrValueProperty); }
set { SetValue(StrValueProperty, value); }
}
/// <summary>
/// "Enter" key down
/// </summary>
public ICommand KeyDownCommand
{
get { return (ICommand)GetValue(KeyDownCommandProperty); }
set { SetValue(KeyDownCommandProperty, value); }
}
}
}
//---The Model--Car.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestMVVM
{
/// <summary>
/// A simple model
/// </summary>
class Car : INotifyPropertyChanged
{
public Car(string name) {
this.name = name;
}
private string name;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
-----Main View Model---
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace TestMVVM
{
/// <summary>
/// The Main View Model
/// </summary>
class MainViewModel : INotifyPropertyChanged
{
/// <summary>
/// The main view model
/// </summary>
public MainViewModel()
{
//Create some test data
cars = new ObservableCollection<Car>();
cars.Add(new Car("Audi"));
cars.Add(new Car("Toyota"));
cars.Add(new Car("Subaru"));
cars.Add(new Car("Volvo"));
}
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Car> cars; //List of tensioner spools
private ICommand enterDownCommand;
public ObservableCollection<Car> Cars
{
get { return cars; }
set
{
cars = value;
OnPropertyChanged("Cars");
}
}
public ICommand EnterDownCommand
{
get
{
if (enterDownCommand == null)
{
enterDownCommand = new RelayMCommand<Car>(OnEnterDownCommand);
}
return enterDownCommand;
}
}
/// <summary>
/// Called when "Enter" key is down.
/// </summary>
/// <param name="obj"></param>
private void OnEnterDownCommand(Car obj)
{
//How do I get the text box value here?
Console.Write(">>"+obj.Name);
}
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
-----MainWindow---
<Window x:Class="TestMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestMVVM"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel x:Name ="MainVM"/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Viewbox>
<ItemsControl ItemsSource="{Binding Cars}" Margin="5" Width="200">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:CarUserControl Margin="5"
KeyDownCommand="{Binding Path=DataContext.EnterDownCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Viewbox>
</Grid>
</Grid>
</Window>
---Relay Command---
using System;
using System.Threading;
using System.Windows.Input;
namespace TestMVVM
{
/// <summary>
/// Same as the Relay Command, except this handles an array of generic type <T>
/// </summary>
/// <typeparam name="T">Generic type parameter</typeparam>
public class RelayMCommand<T> : ICommand
{
private Predicate<T> _canExecute;
private Action<T> _execute;
public RelayMCommand(Action<T> execute, Predicate<T> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
private void Execute(T parameter)
{
_execute(parameter);
}
private bool CanExecute(T parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public bool CanExecute(object parameter)
{
return parameter == null ? false : CanExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
var temp = Volatile.Read(ref CanExecuteChanged);
if (temp != null)
{
temp(this, new EventArgs());
}
}
}
}

A UserControl may inherit its DataContext from a parent window or the current item in an ItemsControl.
So if you bind your ItemsControl to an IEnumerable<Car>, each instance of the CarUserControl can bind directly to the Name property of the corresponding Car object:
<TextBox Text="{Binding Name}"
Grid.Column="1" Background="#FFE8D3D3" BorderThickness="0">
<TextBox.InputBindings>
<KeyBinding Key="Enter"
Command="{Binding KeyDownCommand, ElementName=thisUC}"
CommandParameter="{Binding}"/>
</TextBox.InputBindings>
</TextBox>
This is because the UserControl automatically inherits the DataContext from its parent element which is the corresponding Car object in the ItemsControl in this case.

Related

How can I make the combobox add items faster?

I used a ComboBox to load all the font on the computer and preview it.
Here is the XAML:
<Window x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Sample"
mc:Ignorable="d"
Title="Demo" Height="450" Width="800" WindowStartupLocation="CenterScreen" WindowStyle="None" Loaded="Window_Loaded" Background="White">
<Window.Resources>
<local:FontFamilyConverter x:Key="FontFamilyConverter"></local:FontFamilyConverter>
</Window.Resources>
<Grid>
<ComboBox Margin="10,10,0,10" HorizontalContentAlignment="Stretch" Name="FontFaimlyCB" Height="50" Width="250" ItemsSource="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="{Binding Converter={StaticResource FontFamilyConverter}}" FontSize="20"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
And here is code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Threading;
using System.Globalization;
namespace Sample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
public class FontFamilyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new FontFamily(value.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
When the first time I click the ComboBox, it always takes a long time to add the items(There are almost one thousand fonts on my computer and it will takes 3-4 seconds to finish it). But any other software such as word/photoshop and so on won't like this.
How can I make it add faster? Please help me, thank you.
You could try to use a VirtualizingStackPanel as the ItemsPanel and set the MaxDropDownHeight to a fairly small value in order not to render all containers immediately. And you shouldn't have to use a converter:
<ComboBox Margin="10,10,0,10" HorizontalContentAlignment="Stretch" Name="FontFaimlyCB" Height="50" Width="250"
ItemsSource="{Binding Source={x:Static Fonts.SystemFontFamilies}}"
MaxDropDownHeight="50">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="{Binding}" FontSize="20" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Create your own observable collection that allows you to suspend notifications when adding items. E.g.:
static void Main(string[] args)
{
var fonts = new ObservableCollectionEx<string>();
using (fonts.DeferCollectionChanged())
{
for (int i = 0; i < 100000; i++)
{
fonts.Add(Guid.NewGuid().ToString());
}
}
}
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
private int deferLevel;
private bool collectionUpdateNeeded;
public ObservableCollectionEx()
{
}
public ObservableCollectionEx(IEnumerable<T> collection)
: base(collection)
{
}
public ObservableCollectionEx(List<T> collection)
: base(collection)
{
}
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (deferLevel == 0)
{
CollectionChanged?.Invoke(this, e);
collectionUpdateNeeded = false;
}
else
{
collectionUpdateNeeded = true;
}
}
public IDisposable DeferCollectionChanged()
{
++deferLevel;
return new DeferHelper(this);
}
private void EndDefer()
{
--deferLevel;
if (deferLevel == 0 && collectionUpdateNeeded)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private class DeferHelper : IDisposable
{
private ObservableCollectionEx<T> collection;
public DeferHelper(ObservableCollectionEx<T> collection)
{
this.collection = collection;
}
public void Dispose()
{
if (collection != null)
{
collection.EndDefer();
collection = null;
}
}
}
}

DataTriggers and default property when using ViewModels, Dependency Properties

I have a UserControl and a ViewModel for it.
The UserControl is a simple Button Template, with an Ellipse in it. Basically a round button.
I use the ViewModel as a DataContext for the UserControl.
The ViewModel has a property called "State" whose value I use as a DataTrigger to change "Colors" of the Ellipse, these "Colors" are Dependency Properties in the UserControl.
Everything seems to work, but I cannot set the Default Colors for the Ellipse and because of that I don`t see anything in the Designer for the UserControl. See attached picture.
I do see the correct colors and the shapes when I place the UserControl on to the main window designer.
I definitely would like to see the shapes in the UserControl with some default values, so its easier to see what I`m working with.
This I believe is something to do with DataBinding "State" value from the ViewModel.
Here is the Code : UserControl xaml
<UserControl x:Name="thisBtn" x:Class="WpfAppDelMe.Views.CustomButton"
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:local="clr-namespace:WpfAppDelMe.Views"
xmlns:viewmodel="clr-namespace:WpfAppDelMe.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<viewmodel:CustButtonViewModel x:Name="xvm"/>
</UserControl.DataContext>
<Grid Background="#FF9EE3EA">
<Button Content="{Binding State}" Command="{Binding ClickCommand}" Foreground="#FFD13B3B" Margin="0" >
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Viewbox Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Grid.ColumnSpan="3">
<Ellipse Height="300" Width="300" Margin="5">
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}" >
<!-- Below Line does not work! -->
<Setter Property ="Fill" Value="Pink" />
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="{x:Null}">
<Setter Property="Fill" Value="OldLace"/>
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="0">
<Setter Property="Fill" Value="{Binding ElementName=thisBtn, Path=Color1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="1">
<Setter Property="Fill" Value="{Binding ElementName=thisBtn, Path=Color2}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
</Viewbox>
<!--The content presenter for the button string.
Placing this in a view box makes sure of the correct size.-->
<Viewbox Grid.Row="1" Grid.ColumnSpan="3" Margin="10">
<ContentPresenter/>
</Viewbox>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
UserControl .cs file
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfAppDelMe.Views
{
/// <summary>
/// Interaction logic for CustXam.xaml
/// </summary>
public partial class CustomButton : UserControl, INotifyPropertyChanged
{
/// <summary>
/// Interaction logic for the CustomButton UserControl
/// View and Model
/// ViewModel : CustomButtonViewModel
/// </summary>
public CustomButton()
{
InitializeComponent();
}
public static readonly DependencyProperty Color1Property =
DependencyProperty.Register("Color1", typeof(Brush), typeof(CustButton), new
PropertyMetadata(new SolidColorBrush(Colors.Black)));
public static readonly DependencyProperty Color2Property =
DependencyProperty.Register("Color2", typeof(Brush), typeof(CustButton), new
PropertyMetadata(new SolidColorBrush(Colors.White)));
public event PropertyChangedEventHandler PropertyChanged = delegate { };
//Null initialization is required because there are no listeners or
bindings
for this.
private int btnState=0;
//Button Color 1
public Brush Color1
{
get { return (Brush)GetValue(Color1Property); }
set { SetValue(Color1Property, value); }
}
public Brush Color2
{
get { return (Brush)GetValue(Color2Property); }
set { SetValue(Color2Property, value); }
}
public int BtnState
{
get
{ return btnState;}
set
{
btnState = value;
this.OnPropertyChanged("BtnState");
}
}
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
xvm.State = btnState;
}
}
}
}
[![enter image description here][1]][1]ViewModel for the UseControl
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace WpfAppDelMe.ViewModels
{
/// <summary>
/// View Model class for the CustButton. All logic and calculations happen
here.
/// This is also the DataContext for the CustButton
///
/// View and Model : CustButton
/// ViewModel : CustButtonViewModel
/// </summary>
internal class CustButtonViewModel : INotifyPropertyChanged
{
public CustButtonViewModel()
{
}
private int state;
public event PropertyChangedEventHandler PropertyChanged;
private ICommand clickCommand;
public ICommand ClickCommand
{
get
{
if (clickCommand == null)
{
clickCommand = new RelayCommand(param => ChangeState(), param => CanChange());
}
return clickCommand;
}
}
/// <summary>
///
/// </summary>
private void ChangeState()
{
if (State == 0)
{
State = 1;
}
else
{
State = 0;
}
}
private bool CanChange()
{
return true;
}
//Button state
public int State
{
get{ return state; }
set
{
state = value;
OnPropertyChange("State");
}
}
/// <summary>
/// On Property Changed
/// </summary>
/// <param name="name"></param>
public void OnPropertyChange(string prop)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prop));
}
}
}
}

WPF: can't bind object to listbox

Trying to bind a listbox to an object. Code runs without errors but for some reason sample data doesn't appear in listbox
XAML: ucDataBindingObject.xaml
<UserControl x:Class="TheProject.UserControls.ucDataBindingObject"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Name="DataBindingObject"
Width="Auto"
Height="Auto"
mc:Ignorable="d">
<Grid Width="130"
Height="240"
Margin="0">
<ListBox Name="lbObject"
Width="110"
Height="80"
Margin="10,7,-9.6,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
DisplayMemberPath="Name"
ItemsSource="{Binding ElementName=ucDataBindingObject,
Path=Clients}" />
</Grid>
</UserControl>
C#: ucDataBindingObject.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
namespace TheProject.UserControls
{
public partial class ucDataBindingObject : UserControl
{
public List<Client> Clients { get; set; }
public ucDataBindingObject()
{
Clients = new List<Client>();
Clients.Add(new Client(1, "David")); // sample data
Clients.Add(new Client(2, "Helen"));
Clients.Add(new Client(3, "Joe"));
InitializeComponent();
}
}
C# Client.cs
using System;
using System.Linq;
namespace TheProject.UserControls
{
public class Client
{
public int ID { get; set; }
public string Name { get; set; }
public Client(int id, string name)
{
this.ID = id;
this.Name = name;
}
}
}
Update your ItemsSource Binding as
ItemsSource="{Binding Path=Clients}"
and in the constructor of your view, set its DataContext after InitializeComponents as
this.DataContext = this;
there is no element named ucDataBindingObject, its the class name of your usercontrol
OR change the elementname in binding to DataBindingObject, which you named your usercontrol

Bind a button to the selected item

I have a button and want to change the click handler every time I change tabs. I was hoping to perform this with Binding.
<Window x:Class="BWCRenameUtility.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vpan="clr-namespace:BWCRenameUtility.View.VersionPanels"
Title="MainWindow" Height="526" Width="525">
<Grid>
<DockPanel>
<TextBlock Text="Foo" DockPanel.Dock="Top" TextWrapping="Wrap" Padding="10" />
<Grid DockPanel.Dock="Bottom">
<!-- This is not correct, how do I perform this binding correct? -->
<Button Content="Export..." HorizontalAlignment="Right" Margin="10" Click="{Binding SelectedItem.Content.PerformExport,ElementName=tabcontrol}" />
</Grid>
<TabControl Name="tabcontrol">
<TabItem Header="1.2.5">
<vpan:VersionPanel1_2_5/>
</TabItem>
<TabItem Header="1.2.8">
<vpan:VersionPanel1_2_8/> <!-- These can be of the same Type by inheritance -->
</TabItem>
</TabControl>
</DockPanel>
</Grid>
</Window>
As you can see, the Button.Click is not bound correctly and I want to know how this works in WPF.
You can achieve this with Commands, you will create a ICommand for each of toy TabItem ViewModels and bind the Buttons Command property to that Command
The RelayCommand is a very common way to handle stuff like this and can be used throughout your application
Relay command:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RelayCommand"/> class.
/// </summary>
/// <param name="execute">The execute.</param>
public RelayCommand(Action<object> execute) : this(execute, null) { }
/// <summary>
/// Initializes a new instance of the <see cref="RelayCommand"/> class.
/// </summary>
/// <param name="execute">The action to execute.</param>
/// <param name="canExecute">The can execute.</param>
/// <exception cref="System.ArgumentNullException">execute</exception>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
/// <summary>
/// Defines the method that determines whether the command 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>
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </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)
{
_execute(parameter);
}
#endregion
}
And you would use in the following fashion in your application
ViewModel or Control:
public class VersionPanel1_2_8 : VersionPanel
{
public ICommand MyCommand { get; internal set; }
public VersionPanel1_2_8()
{
MyCommand = new RelayCommand(x => MethodToExecute());
}
private void MethodToExecute()
{
}
}
Xaml:
<Window x:Class="BWCRenameUtility.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vpan="clr-namespace:BWCRenameUtility.View.VersionPanels"
Title="MainWindow" Height="526" Width="525">
<Grid>
<DockPanel>
<TextBlock Text="Foo" DockPanel.Dock="Top" TextWrapping="Wrap" Padding="10" />
<Grid DockPanel.Dock="Bottom">
<!-- This is not correct, how do I perform this binding correct? -->
<Button Content="Export..." HorizontalAlignment="Right" Margin="10" Command="{Binding SelectedItem.Content.MyCommand,ElementName=tabcontrol}" />
</Grid>
<TabControl Name="tabcontrol">
<TabItem Header="1.2.5">
<vpan:VersionPanel1_2_5/>
</TabItem>
<TabItem Header="1.2.8">
<vpan:VersionPanel1_2_8/> <!-- These can be of the same Type by inheritance -->
</TabItem>
</TabControl>
</DockPanel>
</Grid>
</Window>
You need to bind Button's Command property to your command in WPF like,
Command="{Binding SelectedItem.Content.PerformExport, ElementName=tabcontrol}"
Click is an event, if you want you can also do event to command binding (binding any event to command in your viewmodel) however that is not necessary in your case

wpf binding list, listbox and control twoway

Is there any way to update listbox items text after updating it in textboxes? I wanna do it only with bindings if it is possible. Listbox is reading from list, but list isn't updating so it's never gonna change, unless I add new item to list. Here is code
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DockPanel VerticalAlignment="Top" HorizontalAlignment="Left">
<Button Name="dodaj" Width="40" Margin="5" Click="dodaj_Click">Dodaj</Button>
<Button Name="usun" Width="40" Margin=" 5" Click="usun_Click">Usuń</Button>
</DockPanel>
<DockPanel HorizontalAlignment="Left" VerticalAlignment="Center" DataContext="{Binding ElementName=lista, Path=osoba, Mode=TwoWay}">
<ListBox Name="lb" ItemsSource="{Binding}" Width="200" Height="230">
</ListBox>
</DockPanel>
<DockPanel HorizontalAlignment="Right" VerticalAlignment="top">
<WrapPanel>
<StackPanel>
<Label>Imię</Label>
<Label>Nazwisko</Label>
<Label>Email</Label>
<Label>Telefon</Label>
</StackPanel>
<StackPanel DataContext="{Binding ElementName=lb, Path=SelectedItem, UpdateSourceTrigger=LostFocus}" TextBoxBase.TextChanged="zmiana">
<TextBox Width="200" Margin="3" Name="imie" Text="{Binding Path=imie}"></TextBox>
<TextBox Width="200" Name="nazwisko" Text="{Binding Path=nazwisko}"></TextBox>
<TextBox Width="200" Margin="3" Name="email" Text="{Binding Path=email}"></TextBox>
<TextBox Width="200" Name="telefon" Text="{Binding Path=telefon}"></TextBox>
</StackPanel>
</WrapPanel>
</DockPanel>
</Grid>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lista.Add(new dane("imie1", "nazwisko1", "email1", "tel1"));
lista.Add(new dane("imie2", "nazwisko2", "email2", "tel2"));
lb.DataContext = lista;
}
public class dane : INotifyPropertyChanged
{
public dane(string imie, string nazwisko, string email, string telefon)
{
this._imie = imie;
this.nazwisko = nazwisko;
this.osoba = nazwisko + " " + imie;
this.email = email;
this.telefon = telefon;
}
private string _imie;
public string imie
{
set
{
_imie = value;
OnPropertyChanged("Imie");
}
get
{
return _imie;
}
}
public string nazwisko { set; get; }
public string osoba { set; get; }
public string email { set; get; }
public string telefon { set; get; }
public override string ToString()
{
return osoba;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string value)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(value));
}
}
}
public ObservableCollection<dane> lista = new ObservableCollection<dane>();
//public List<dane> lista = new List<dane>();
/*public ObservableCollection<dane> lista
{
get { return (ObservableCollection<dane>)GetValue(listaProperty); }
set { SetValue(listaProperty, value); }
}
public static readonly DependencyProperty listaProperty =
DependencyProperty.Register("lista", typeof(ObservableCollection<dane>), typeof(Window), new UIPropertyMetadata(null));
*/
private void dodaj_Click(object sender, RoutedEventArgs e)
{
lista.Add(new dane("...", "", "", ""));
MessageBox.Show(lista[0].ToString());
}
private void usun_Click(object sender, RoutedEventArgs e)
{
lista.RemoveAt(lb.SelectedIndex);
}
}
}
Listbox shows instances of your dane class by their ToString() representation. You should bind listbox to osoba property directly with DisplayMemberPath. And also value of osoba property is not udated then you change imia and nazwisko. You should recalculate it on its getter and also raise PropertChanged("osoba") then properties nazwisko or imia are being changed.

Resources