Set value of DependencyProperty to ViewModel property - c#

Not sure if what I want is possible, but I also wouldn't know why it isn't.
I have a user control with a dependency property (a string) which I define in XAML e.g. as follows:
<Window ... (EngraveUnitWindow)
DataContext = EngraveUnitViewModel
...
...
<parameters:DoubleParameterUserControl
DisplayName="Exchanger Offset [deg]"
DataContext="{Binding ExchangerOffset}"/>
The view model 'EngraveUnitViewModel' :
public class EngraveUnitViewModel : ViewModelBase, IUnitViewModel
...
...
public DoubleParameterViewModel ExchangerOffset { get; }
What I want to achieve, is set the value of DisplayName to ParameterName property in the DoubleParameterViewModel. So I created a Style which binds the DisplayName to the viewmodel as follows:
<UserControl.Resources>
<Style TargetType="parameters:DoubleParameterUserControl">
<Setter Property="DisplayName" Value="{Binding ParameterName, Mode=OneWayToSource}"/>
</Style>
</UserControl.Resources>
The complete DoubleParameterUserControl code below:
<UserControl
...
...
d:DataContext="{d:DesignInstance viewModels:DoubleParameterViewModel, d:IsDesignTimeCreatable=False}"
Margin="5">
<UserControl.Resources>
<Style TargetType="parameters:DoubleParameterUserControl">
<Setter Property="DisplayName" Value="{Binding ParameterName, Mode=OneWayToSource}"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding ElementName=DoubleParameter, Path=DisplayName}" VerticalAlignment="Center" Margin="5,0,0,0" />
<Border Grid.Column="1" BorderThickness="1" BorderBrush="LightGray" Margin="0, 0, 5, 0">
<TextBlock
VerticalAlignment="Center"
Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Margin="5, 0, 5, 0">
<TextBlock.InputBindings>
<MouseBinding Gesture="LeftClick" Command="{Binding ShowNumpadCommand}" />
</TextBlock.InputBindings>
</TextBlock>
</Border>
<Button x:Name="_button" Grid.Column="2" MinWidth="30" MinHeight="30" Content="..." Command="{Binding ShowNumpadCommand}"/>
</Grid>
</UserControl>
And its code behind (where I define the DependencyProp:
public partial class DoubleParameterUserControl
{
public string DisplayName
{
get => (string)GetValue(DisplayNameProperty);
set => SetValue(DisplayNameProperty, value);
}
public static readonly DependencyProperty DisplayNameProperty =
DependencyProperty.Register(nameof(DisplayName), typeof(string), typeof(DoubleParameterUserControl),
new PropertyMetadata(""));
public DoubleParameterUserControl()
{
InitializeComponent();
_button.Focus();
}
}
For completeness, the viewmodel:
public class DoubleParameterViewModel : ViewModelBase
{
private readonly Parameter<double> parameter;
private double value;
public RelayCommand ShowNumpadCommand { get; }
public string ParameterName { get; set; }
public double Value
{
get => parameter.Value;
set
{
parameter.Value = value;
Set(() => Value, ref this.value, value);
}
}
public DoubleParameterViewModel(Parameter<double> parameter)
{
this.parameter = parameter;
ShowNumpadCommand = new RelayCommand(ShowNumpad);
}
private void ShowNumpad()
{
var numpadViewModel = new VirtualKeypadsViewModel(true)
{
ParameterName = ParameterName,
Input = Value.ToString("F2", CultureInfo.InvariantCulture)
};
var numpad = new Numpad
{
Owner = System.Windows.Application.Current.MainWindow,
DataContext = numpadViewModel
};
if (numpad.ShowDialog() == true)
{
Value = numpadViewModel.ResultAsDouble();
}
}
}
Just to be clear, the property ParameterName in the ViewModel never gets set. So in my code, I want to popup a Numpad dialog which shows the parameter name in its title bar, but the ParameterName did not receive the bound DisplayName.
I hope somebody can explain me how I can solve that. (or, that it is not possible, and why not if that would sadly be the case)

It seems like DoubleParameterViewModel.ParameterName exists solely to provide a name when executing ShowNumpadCommand. If that's the case, forget the property and just pass DisplayName as your command parameter.
public ICommand ShowNumpadCommand { get; }
public DoubleParameterViewModel(Parameter<double> parameter)
{
this.parameter = parameter;
ShowNumpadCommand = new RelayCommand<string>(ShowNumpad);
}
private void ShowNumpad(string parameterName)
{
/* ... */
}
Get rid of the Style, and bind your button's command parameter to its owner's DisplayName:
<UserControl x:Name="EditorRoot">
<!-- ... -->
<Button x:Name="_button"
Command="{Binding ShowNumpadCommand}"
CommandParameter="{Binding ElementName=EditorRoot, Path=DisplayName}" />
<!-- ... -->
</UserControl>

Related

ItemsControl DataTemplate not binding to underlining data

I am having a very difficult time trying to get this to two-way bind to the item in the collection. The strange thing here is that the control shows the label but when I type anything in the text box it doesn't set the underlining value. Can someone please tell me what I'm doing wrong here.
<ItemsControl Grid.Row="0" Grid.RowSpan="2" ItemsSource="{Binding QueryObject.RequiredParameters}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type queryModels:QueryObjectParameter}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="{Binding Label}"></Label>
<TextBox Grid.Row="1" Text="{Binding Value, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I've tried these different types.
{Binding Path=Value, RelativeSource={RelativeSource Self} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
{Binding XPath=DataContext.Value, RelativeSource={RelativeSource Self} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
{Binding XPath=Value, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
{Binding Path=Value, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}} , Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}
Thank you for your help!
Edit:
I have been asked to add a better example to this so I created a very easy example. Note: the underlining model is called, but it doesn't set the model in the ViewModel.
public class MainWindowViewModel:INotifyPropertyChanged
{
public MainWindowViewModel()
{
PersonQuery = new PersonQuery();
Command = new DelegateCommand(CommandAction);
}
private void CommandAction()
{
MessageBox.Show(PersonQuery.Parameters.First().ToString());
}
public DelegateCommand Command { get; set; }
public PersonQuery PersonQuery { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Parameter
{
public Parameter(string label)
{
Label = label;
}
public string Label { get; set; }
public object Value { get; set; }
public override string ToString()
{
return $"{Label}:{Value}";
}
}
public class PersonQuery
{
public Parameter[] Parameters => new[] {new Parameter("Test"),};
}
XAML:
<Button Content="Run" Command="{Binding Command}"></Button>
<ItemsControl Grid.Row="1" ItemsSource="{Binding PersonQuery.Parameters}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:Parameter}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Label Content="{Binding Label}"></Label>
<TextBox Grid.Row="1" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here's a simplified example where an ItemsControl is used and property update works both ways.
Simple Data class:
public class Data
{
public int Id { get; set; }
public string Name { get; set; }
}
Here I implemented INotifyPropertyChanged on my MainWindow for convenience, but you should really use a ViewModel and do this there.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<Data> _dataList = null;
public ObservableCollection<Data> DataList
{
get { return _dataList; }
set
{
_dataList = value;
OnPropertyChanged("DataList");
}
}
public MainWindow()
{
InitializeComponent();
DataList = new ObservableCollection<Data>
{
new Data() { Id = 1, Name = "Dan" },
new Data() { Id = 2, Name = "Julie" }
};
DataContext = this;
}
}
The XAML is super simple:
<ItemsControl ItemsSource="{Binding DataList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Id}"/>
<TextBox Grid.Column="1" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
NOTE:
Check in your code if you have:
Implemented INotifyPropertyChanged
Use an ObservableCollection for your list.

Remove ListBoxItem on button click through ICommands

I just started with XAML/WPF and there are lots of questions going on in my head. One of them is how do we bind a button click to remove a ListBoxItem through the ICommand interface. I created a simple WPF project and here's my XAML:
<ListBox Name="lb" HorizontalAlignment="Left" Height="129" Margin="15,17,0,0" VerticalAlignment="Top" Width="314" Grid.ColumnSpan="2" >
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Height" Value="30" />
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel Orientation="Horizontal">
<CheckBox Margin="5,5" Height="18" IsChecked="{TemplateBinding IsSelected}">
<ContentPresenter Content="{TemplateBinding Content}"/>
</CheckBox>
<Button Content="[x]" Height="22" Width="22" HorizontalAlignment="Right"
Command="{Binding ElementName=lb, Path=DataContext.DeleteItemCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}" CommandParameter="{Binding }"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<ListBoxItem Content="Foo" />
<ListBoxItem Content="Bar" />
</ListBox>
And here's my Window:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Context(); // Also tried before InitializeComponent()
}
public class Context
{
public ICommand DeleteItemCommand = new DeleteItemCommand();
}
}
Where DeleteItemCommand is:
public class DeleteItemCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
MessageBox.Show("Meep");
}
}
The questions are:
Why isn't the message box showing? How do I make it work?
How do I retrieve which index/ListBoxItem triggered the button
click?
How do I align the button to the end of the line?
Thanks a lot!
One problem you have there is your ICommand is just a variable.
You need a public property in order to bind.
More like
public ICommand DeleteItemCommand {get;set;} = new DeleteItemCommand();
Another problem is your elementname. This is subject to namescope and I think you'll find the listbox is in another namescope.
Instead, just use relativesource binding with ancestortype ListBox.
Roughly.
Command="{Binding DataContext.DeleteItemCommand,
RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}
As an aside.
I recommend looking into a framework to make commands and suchlike easier.
MVVMLight would be my suggestion. Add to a project using nuget mvvmlightlibs. https://msdn.microsoft.com/en-gb/magazine/dn237302.aspx?f=255&MSPPError=-2147217396
The following is based on some code I already had so it's illustrative rather than exactly what you're doing.
View:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding People}"
HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding LastName}"/>
<Button Content="Delete"
Command="{Binding DataContext.DeletePersonCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding}"
Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Viewmodel uses relaycommand from mvvmlight
using GalaSoft.MvvmLight.CommandWpf;
using System.Collections.ObjectModel;
namespace wpf_99
{
public class MainWindowViewModel : BaseViewModel
{
private RelayCommand<Person> deletePersonCommand;
public RelayCommand<Person> DeletePersonCommand
{
get
{
return deletePersonCommand
?? (deletePersonCommand = new RelayCommand<Person>(
(person) =>
{
People.Remove(person);
}
));
}
}
private ObservableCollection<Person> people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return people; }
set { people = value; }
}
public MainWindowViewModel()
{
People.Add(new Person { FirstName = "Chesney", LastName = "Brown" });
People.Add(new Person { FirstName = "Gary", LastName = "Windass" });
People.Add(new Person { FirstName = "Liz", LastName = "McDonald" });
People.Add(new Person { FirstName = "Carla", LastName = "Connor" });
}
}
}
BaseViewModel is pretty much as the msdn article on inotifypropertychanged shows:
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Mvvmlight has its own base viewmodel but you can't serialise a vm inherits from that.
Person:
public class Person : BaseViewModel
{
private string firstName;
public string FirstName
{
get { return firstName; }
set { firstName = value; RaisePropertyChanged(); }
}
private string lastName;
public string LastName
{
get { return lastName; }
set { lastName = value; RaisePropertyChanged(); }
}

Wpf bind property in ItemContainerStyle to TextBlock in DataTemplate of ItemTemplate

I want to bind AutomationProperties.Name to text that containd in secondTextBox how to do that? Using only xaml without code behind.
Xaml:
<Window x:Class="WpfApplication5.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:WpfApplication5"
xmlns:converters="clr-namespace:WpfApplication5.Converters"
mc:Ignorable="d"
Title="TestWindow" Height="350" Width="525">
<Window.Resources>
<converters:StrangeConverter x:Key="CommonConverter"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<ComboBox Height="20" Width="100" ItemsSource="{Binding comboBoxItems}" AutomationProperties.AutomationId="ID_COMBO1">
<ComboBox.ItemContainerStyle>
<Style>
<!--<Setter Property="AutomationProperties.Name" Value="{Binding UniqNumber}"/>-->
<Setter Property="AutomationProperties.Name" Value="{Binding Path=Text, ElementName=secondTextBox}"/>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="secondTextBox">
<Run Text="{Binding Name}"></Run>
<Run Text="-"></Run>
<Run Text="{Binding UniqNumber}"></Run>
<Run Text="{Binding .,Converter={StaticResource CommonConverter}}"></Run>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
<ComboBox Height="20" Width="100" ItemsSource="{Binding comboBoxItems}" AutomationProperties.AutomationId="ID_COMBO2"/>
<Button Grid.Row="1" Height="20" Width="100" Click="Button_Click"/>
</Grid>
</Window>
Converter:
namespace WpfApplication5.Converters
{
class StrangeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return "Test";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Code:
namespace WpfApplication5
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ViewModel Vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = Vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var _calculatorAutomationElement = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "TestWindow"));
var combobox = _calculatorAutomationElement.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.AutomationIdProperty, "ID_COMBO1"));
Vm.SelectComboboxItem(combobox, "02 - Basic Get");
combobox = _calculatorAutomationElement.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.AutomationIdProperty, "ID_COMBO2"));
Vm.SelectComboboxItem(combobox, "01 - Basic Set");
}
}
public class ViewModel
{
public List<Item> comboBoxItems { get; set; }
public ViewModel()
{
comboBoxItems = new List<Item>();
comboBoxItems.Add(new Item { Name = "Basic Get", UniqNumber = 1 });
comboBoxItems.Add(new Item { Name = "Basic Set", UniqNumber = 2 });
comboBoxItems.Add(new Item { Name = "Basic Report", UniqNumber = 3 });
}
public bool SelectComboboxItem(AutomationElement comboBox, string item)
{
(comboBox.GetCurrentPattern(ExpandCollapsePattern.Pattern) as ExpandCollapsePattern).Expand();
PropertyCondition findCondition = new PropertyCondition(AutomationElement.NameProperty, item);
var comboBoxItems = comboBox.FindFirst(TreeScope.Children, findCondition);
if (comboBoxItems != null)
{
var selectionItemPattern = comboBoxItems.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern;
selectionItemPattern.Select();
return true;
}
return false;
}
}
public class Item
{
public string Name { get; set; }
public int UniqNumber { get; set; }
}
}
Update the Item class as:
public class Item
{
private string _TextToDisplay;
public string Name { get; set; }
public int UniqNumber { get; set; }
public string TextToDisplay
{
get
{
_TextToDisplay = Name + "-" + UniqNumber; //Add other modification from converter
return _TextToDisplay;
}
set
{
_TextToDisplay = value;
}
}
}
Bind the AutomationProperties.Name to this property
<TextBlock x:Name="secondTextBox" Text="{Binding TextToDisplay}" AutomationProperties.Name="{Binding TextToDisplay}">
This should work without additional view model property and without converter:
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="AutomationProperties.Name">
<Setter.Value>
<MultiBinding StringFormat="{}{0} - {1} Test">
<Binding Path="Name"/>
<Binding Path="UniqNumber"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1} Test">
<Binding Path="Name"/>
<Binding Path="UniqNumber"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>

Binding WPF Control Name to different Control

Have style for a series of buttons btn1, btn2, btn3, etc.
Inside the style for each button is a TextBlock for displaying the "Content" of the button, (since the border inside the style covers any content of the button itself).
Now, I would like for the TextBlock name to be tied to the button name. For example - btn1's text block's name would be btn1Txt. The purpose of this would be the end user can assign each button its own text in a settings menu.
Any hints on how I would go about this? I admit I'm relatively new to WPF and bindings.
EDIT:::: WHAT I"VE GOT SO FAR THAT IS WORKING.
On load, the program checks the settings file for the Text for each button. Each button's content is assigned the proper information. Then inside the style, I bind the TextBlock Text to the content of the parent button.
This may not be the normal way of going about it, but it works
Method
List<string> MainButtons = Properties.Settings.Default.MainButtonNames.Cast<string>().ToList();
for (int i = 0; i < MainButtons.Count(); i++)
{
string actualNum = Convert.ToString((i + 1));
var MainButtonFinder = (Button)this.FindName("MainButton" + actualNum);
Console.WriteLine(MainButtonFinder.Name);
MainButtonFinder.Content = MainButtons[i];
Console.WriteLine(MainButtonFinder.Content);
}
Style
<Style x:Key="MainButtonStyle" TargetType="{x:Type Button}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Width" Value="100px"/>
<Setter Property="MinHeight" Value="50"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="20" Height="45" Width="100" Margin="0" Background="#FF99CCFF">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content}" HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="LCARS" Foreground="White" Padding="5px" FontSize="18px" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>`
This is the wrong way to go about what you're trying to do. Here's the "right" way to do it. There's a fair amount of boilerplate code here, but you get used to it.
Write a button viewmodel and give your main viewmodel an ObservableCollection of those:
#region ViewModelBase Class
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
#endregion INotifyPropertyChanged
}
#endregion ViewModelBase Class
#region MainViewModel Class
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
ButtonItems.Add(new ButtonItemViewModel("First Command", "First Item", () => MessageBox.Show("First Item Executed")));
ButtonItems.Add(new ButtonItemViewModel("Second Command", "Second Item", () => MessageBox.Show("Second Item Executed")));
}
#region ButtonItems Property
public ObservableCollection<ButtonItemViewModel> ButtonItems { get; }
= new ObservableCollection<ButtonItemViewModel>();
#endregion ButtonItems Property
}
#endregion MainViewModel Class
#region ButtonItemViewModel Class
public class ButtonItemViewModel : ViewModelBase
{
public ButtonItemViewModel(String cmdName, String text, Action cmdAction)
{
CommandName = cmdName;
Text = text;
Command = new DelegateCommand(cmdAction);
}
#region Text Property
private String _text = default(String);
public String Text
{
get { return _text; }
set
{
if (value != _text)
{
_text = value;
OnPropertyChanged();
}
}
}
#endregion Text Property
#region CommandName Property
private String _commandName = default(String);
public String CommandName
{
get { return _commandName; }
private set
{
if (value != _commandName)
{
_commandName = value;
OnPropertyChanged();
}
}
}
#endregion CommandName Property
public ICommand Command { get; private set; }
}
#endregion ButtonItemViewModel Class
public class DelegateCommand : ICommand
{
public DelegateCommand(Action action)
{
_action = action;
}
private Action _action;
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_action?.Invoke();
}
}
Make that MainViewModel the DataContext of your Window:
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
And here's how you can put it all together in the XAML:
<Grid>
<StackPanel Orientation="Vertical">
<GroupBox Header="Buttons">
<ItemsControl ItemsSource="{Binding ButtonItems}" HorizontalAlignment="Left">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Margin="2"
MinWidth="80"
Content="{Binding Text}"
Command="{Binding Command}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
<GroupBox Header="Edit Buttons">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox
Grid.Column="0"
Margin="2"
x:Name="ButtonEditorListBox"
ItemsSource="{Binding ButtonItems}"
HorizontalAlignment="Left"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="2"
Text="{Binding CommandName}"
/>
<TextBlock
Margin="2"
Text="{Binding Text, StringFormat=': "{0}"'}"
/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<ContentControl
Grid.Column="1"
Margin="8,2,2,2"
Content="{Binding SelectedItem, ElementName=ButtonEditorListBox}"
>
<ContentControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock
Margin="2"
HorizontalAlignment="Stretch"
FontWeight="Bold"
Text="{Binding CommandName, StringFormat={}{0}: }"
/>
<TextBox
Margin="2"
HorizontalAlignment="Stretch"
Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
/>
</StackPanel>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</GroupBox>
</StackPanel>
</Grid>
Back to your question:
Inside the style for each button is a TextBlock for displaying the "Content" of the button, (since the border inside the style covers any content of the button itself).
You're doing styles wrong. Very, very wrong. I can help you fix it if you show me the style.
As I understand what you want is to modify the "text" of the button, it occurs to me that you can do it this way.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBox Text="{Binding ElementName=ButtonTest, Path=Content, UpdateSourceTrigger=PropertyChanged}" ></TextBox>
<Button Name="ButtonTest" Grid.Row="1" Width="100" Height="40">
<Button.ContentTemplate>
<DataTemplate>
<TextBlock Foreground="Blue" Text="{Binding}"></TextBlock>
</DataTemplate>
</Button.ContentTemplate>
</Button>
</Grid>

Adding Items to Collection using MVVM pattern

I am having truble accessing ObservableCollection (which is my ItemsSource) from command attached to each of the items.
I am trying make two list, one with all the objects and the second one with objects picked by user.
Here is my view model.
class ViewModel : VMBase
{
private ObservableCollection<Card> _cardsCollection;
public ObservableCollection<Card> CardsCollection
{
get { return _cardsCollection; }
set { _cardsCollection = value; }
}
static private ObservableCollection<Card> _pickedCards;
static public ObservableCollection<Card> PickedCards
{
get { return _pickedCards; }
set { _pickedCards = value;
NotifyPropertyChanged("PickedCards");
}
}
}
class Card : VMBase
{
public string Name { get; set; }
public Card(string name, int cost, CardType type, CardRarity rarity)
{
this.Name = name;
this.BackgroundImage = String.Format("/Images/Cards/{0}.png", name);
this.PickCardCommand = new MvvmCommand();
this.PickCardCommand.CanExecuteFunc = obj => true;
this.PickCardCommand.ExecuteFunction = PickCard;
}
public MvvmCommand PickCardCommand { get; set; }
public void PickCard(object parameter)
{
PickedCards.Add(currentCard);
//Above Does not work, not accessible
CreateDeckModel.PickedCards.Add(currentCard);
//Above does work but only if Collection is static
//but if collection is static I am unable to call NotifyPropertyChanged()
}
}
Here is my XAML file with binding
<GridView Grid.Row="1" ItemsSource="{Binding CardsCollection, Mode=TwoWay}">
<GridView.ItemTemplate>
<DataTemplate>
<Grid>
<Button Height="258" Width="180" Content="{Binding}" Margin="0,0,0,0"
Command="{Binding PickCardCommand}" CommandParameter="{Binding}">
<Button.Template>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<Border BorderThickness="2" BorderBrush="White" Height="258" Width="180">
<Border.Background>
<ImageBrush ImageSource="{Binding BackgroundImage}" />
</Border.Background>
</Border>
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
Here is my MvvmCommand Class
class MvvmCommand : ICommand
{
public Predicate<object> CanExecuteFunc { get; set; }
public Action<object> ExecuteFunction { get; set; }
public void Execute(object parameter)
{
ExecuteFunction(parameter);
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return CanExecuteFunc(parameter);
}
}
}
Is there a way to access ItemsSource from Item or DataContext alternatively make command accessible for ViewModel Class?
You can point the Command to your ViewModel class by changing the button in your xaml file to the following:
<Button Height="258" Width="180" Content="{Binding}" Margin="0,0,0,0" Command="{Binding DataContext.PickCardCommand,RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type vw:ViewClass}}}" CommandParameter="{Binding}">
In the RelativeSource binding you will need to change the following:
vw is the namespace for your View, this will have to be declared with the other namespaces in your xaml file.
ViewClass is the name of your class.
Then you obviously need to move the Command over to the ViewModel class from your Card class.
Windows Phone
<GridView x:Name="myGridView" Grid.Row="1" ItemsSource="{Binding CardsCollection, Mode=TwoWay}">
<GridView.ItemTemplate>
<DataTemplate>
<Grid>
<Button Height="258" Width="180" Content="{Binding}" Margin="0,0,0,0"
Command="{Binding ElementName=myGridView,
Path=DataContext.PickCardCommand}" CommandParameter="{Binding}">
<Button.Template>
<ControlTemplate>
<StackPanel Orientation="Vertical">
<Border BorderThickness="2" BorderBrush="White" Height="258" Width="180">
<Border.Background>
<ImageBrush ImageSource="{Binding BackgroundImage}" />
</Border.Background>
</Border>
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
You will see that I have now named the GridView and then used the name of the GridView in the binding as the ElementName. I believe this should work.
You can just pass the Add method of PickedCards to the Card when you create it:
class Card : VMBase
{
private readonly Action<Card> _addCard;
public Card(..., Action<Card> addCard)
{
...
_addCard = addCard;
this.PickCardCommand = new MvvmCommand();
this.PickCardCommand.CanExecuteFunc = obj => true;
this.PickCardCommand.ExecuteFunction = PickCard;
}
public MvvmCommand PickCardCommand { get; set; }
public void PickCard(object parameter)
{
_addCard(this);
}
}
Then when you create the card:
var card = new Card(..., ..., ..., ..., PickedCards.Add)
You can bind your collection to the Command parameter. Command parameter is currently bound to Item DataSource and not collection
CommandParameter="{Binding}"
Instead use RelativeBinding and bind to itemSource of grid

Categories

Resources