How to execute command on button in WPF? - c#

I need to have a button in each ListViewItem. I've created the Button in DataTemplate, bound the command and it doesn't get executed when I press the button. It just doesn't being called.
I was referring to different tutorials and questions like
WPF Button doesn't execute Command or How to bind WPF button to a command in ViewModelBase? and created a RelayCommand class, which implements ICommand.
Actually, I need to call the action with the parameter, but I can't even get it to work without parameters, so I'm planning to get to it next. Everything else is bound perfectly and works like a charm.
View
<Page.Resources>
<CollectionViewSource x:Key='src'
Source="{Binding TimesheetEntries}"
>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Date" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Page.Resources>
<Page.DataContext>
<ViewModels:TimesheetViewModel/>
</Page.DataContext>
<ListView
x:Name="TimesheetEntriesListView"
Margin="10"
Grid.Row="1"
Grid.ColumnSpan="2"
ItemsSource="{Binding Source={StaticResource src}}"
SelectedItem="{Binding SelectedEntry, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="30" Margin="3" IsEnabled="{Binding IsEditable}">
<ComboBox
SelectedValuePath="Key" DisplayMemberPath="Value"
ItemsSource="{Binding EmploymentTypesDictionary, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding SelectedEmployment, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="300"/>
<TextBox
Text="{Binding Hours, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat=N2}"
Margin="5,0,0,0"
Height="Auto"
IsEnabled="{Binding HoursAvaliable}"
Width="70"/>
<Button Margin="5,0,10,0"
Content="+"
Command="{Binding AddNewTimesheetEntryCommand}"
CommandParameter="{Binding Path=Name}"
></Button>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel Margin="5,5,5,0" Orientation="Horizontal">
<TextBlock FontSize="14" Text="{Binding Path=Name, StringFormat='{}{0:dd/MM/yyyy, dddd}'}"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
ViewModel
class TimesheetViewModel : BaseViewModel
{
public ICommand AddNewTimesheetEntryCommand
{
get
{
return _AddNewTimesheetEntryCommand ?? new RelayCommand(AddNewTimesheetEntry);
}
}
private ICommand _AddNewTimesheetEntryCommand;
public void AddNewTimesheetEntry(object parameter)
{
//Do stuff
}
public TimesheetViewModel()
{
}
}
RelayCommand
public class RelayCommand : ICommand
{
private Action<object> mAction;
public event EventHandler CanExecuteChanged = (sender, e) => { };
public RelayCommand(Action<object> action)
{
mAction = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
mAction(parameter);
}
}

Your button need to have been different bind, beacuse inside the list-template you do not have access to global DataContext only to local. You need to use relative source to access global DataContext.
Command="{Binding Path=DataContext.AddNewTimesheetEntryCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Page}}}"

Related

Binding Tag property of checkbox to ancestor datacontext

I know this is a frequently question but after viewing lots of question in this context i still did not find working solution.
I have this MainWindow
public partial class MainWindow : Window
{
public ObservableCollection<Camera> Cameras { get; set; } = new ObservableCollection<Camera>();
public ObservableCollection<Group> Groups { get; set; } = new ObservableCollection<Group>();
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
And this class
public class Group : INotifyPropertyChanged
{
private int _number;
[XmlAttribute("Number")]
public int Number
{
get { return _number; }
set
{
_number = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
This is part of the MainWindow.xaml (the relevant part)
<StackPanel>
<Button Click="Button_Click_1" Margin="55,0,0,0" Padding="4">Add Group</Button>
<ListView Grid.ColumnSpan="3" Grid.Row="1" ItemsSource="{Binding Groups,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Margin="3" Width="30" Grid.Column="0" Text="{Binding Number}"></TextBlock>
<ComboBox Margin="3" Width="50" Grid.Column="5" ItemsSource="{Binding DataContext.Cameras, RelativeSource={RelativeSource AncestorType=ListView}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.Number}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
<!--<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding Path=DataContext.Number, RelativeSource={RelativeSource AncestorType=ListView}}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />-->
<TextBlock Text="{Binding Path=Name, Mode=TwoWay}"></TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
The list of checkbox inside the comboxbox will be filled according to the list of the cameras.
I want to bind the property "Tag" of the inner checkbox to the member Groups.Number like i did with the textblock above it.
The reason behind this (maybe you have another solution) is that the list of groups is a dynamic group, and i want to identify from which group the checkbox was checked.
I've tried everything with the ancestor issue but nothing seems to work.
other things i've tried are:
<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.Number}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.Groups.Number}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=Number}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
<CheckBox Width="20" VerticalAlignment="Center" Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=Groups.Number}" Checked="OnCbObjectCheckBoxChecked" Unchecked="OnCbObjectCheckBoxChecked" />
What do i miss here?
Ty!
ListView has the wron DataContext. It is outside the DataTemplate and is set to MainWindow. The DataTemplate that targets Group has the proper DataContext, of course the current Group item. You must chose an element of this DataTemplate as binding source. You couls bind to the ComboBox.DataContext:
<CheckBox Tag="{Binding RelativeSource={RelativeSource AncestorType=ComboBox}, Path=DataContext.Number}" />

WPF Window to edit the user.settings. Why values are not changing?

I'm working on a wpf window to edit the user settings.
This is what I did so far:
<ListView Grid.Row="1"
ItemsSource="{Binding Source={x:Static properties:Settings.Default}, Path=PropertyValues}"
HorizontalContentAlignment="Stretch" Background="LightGray"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel HorizontalAlignment="Stretch"
IsEnabled="{Binding DataContext.Enabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<Label Width="200" Content="{Binding Name}"/>
<Label Width="200" Content="{Binding Path=Property.PropertyType}" Foreground="Gray" FontStyle="Italic"/>
<ContentControl VerticalContentAlignment="Center" Content="{Binding Path=PropertyValue}">
<ContentControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Int32}">
<TextBox Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ResourceDictionary>
</ContentControl.Resources>
</ContentControl>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The values are showing but they're not updated in Properties.Settings.Default when I change and save them with Properties.Settings.Default.Save();.
Is the two way binding correct?
Thanks
As suggested in the comments and in this answer, I need to encapsulate the System.Configuration.SettingsPropertyValue into a ViewModel that implements INotifyPropertyChanged. Otherwise the binding won't work.
ViewModel:
public class SettingsPropertyValueProxy : INotifyPropertyChanged
{
public string Name { get; }
public Type PropertyType => PropertyValue.GetType();
public object PropertyValue
{
get
{
return Properties.Settings.Default[Name];
}
set
{
try
{
Properties.Settings.Default[Name] = Convert.ChangeType(value, PropertyType);
Properties.Settings.Default.Save();
}
catch
{ }
}
}
public SettingsPropertyValueProxy(string name)
{
Name = name;
Properties.Settings.Default.PropertyChanged += (sender, e) => _OnPropertyChanged(e.PropertyName);
}
private void _OnPropertyChanged(string propertyName)
{
if (propertyName == Name) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PropertyValue)));
}
public event PropertyChangedEventHandler PropertyChanged;
}
New Property to bind:
public IEnumerable<SettingsPropertyValueProxy> Values { get; }
= Properties.Settings.Default.Properties
.Cast<SettingsProperty>()
.Select(p => new SettingsPropertyValueProxy(p.Name))
.OrderBy(p => p.Name)
.ToArray();
Correct View and Correct DataTemplates:
<ListView Grid.Row="1"
ItemsSource="{Binding Path=Values}"
HorizontalContentAlignment="Stretch" Background="LightGray"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<DockPanel HorizontalAlignment="Stretch"
IsEnabled="{Binding DataContext.Enabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<Label Width="200" Content="{Binding Path=Name}"/>
<Label Width="200" Content="{Binding Path=PropertyType}" Foreground="Gray" FontStyle="Italic"/>
<!--<TextBox Text="{Binding Path=PropertyValue, Mode=TwoWay}"/>-->
<ContentControl VerticalContentAlignment="Center" Content="{Binding Path=PropertyValue}">
<ContentControl.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Path=PropertyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}, Path=DataContext}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding Path=PropertyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}, Path=DataContext}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type sys:Int32}">
<TextBox Text="{Binding Path=PropertyValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}, Path=DataContext}"/>
</DataTemplate>
</ResourceDictionary>
</ContentControl.Resources>
</ContentControl>
</DockPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

ItemsControl does not update the ItemsSource binding

I have an ObservableCollection<string> bound to a ItemsControl as the ItemsSource, the binding works fine from the VM to the View but if I change the content of the binding in the TextBox it will not update the ObservableCollection that it is bound to.
I can't seem to work out why, does anyone know why this is?
Here is my code:
<ItemsControl ItemsSource="{Binding Metrics, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Grid.Column="1" Grid.Row="1" Margin="0, 20, 0, 0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel Orientation="Horizontal">
<TextBox Name="CalibrationNameTB" Grid.Column="1" Text="{Binding ., UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Style="{StaticResource baseStyle}" Margin="0, 1" Padding="5, 1" Width="270" FontSize="12"/>
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl" >
<StackPanel Orientation="Horizontal" >
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
You can't update a string because it's immutable.
What you should do is to replace the ObservableCollection<string> with an ObservableCollection<YourType> where YourType is a class with a public string property that you can get or set:
class YourType : INotifyPropertyChanged
{
private string _theString;
public string TheString
{
get { return _theString; }
set { _theString = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Then you bind to this property in your XAML markup:
<WrapPanel Orientation="Horizontal">
<TextBox Name="CalibrationNameTB" Grid.Column="1" Text="{Binding TheString, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource baseStyle}" Margin="0, 1" Padding="5, 1" Width="270" FontSize="12"/>
</WrapPanel>

Windows Phone - custom control how propagate event to ViewModel

I am creating custom control for menu and I have ListBox in my control. Something like this:
<ListBox x:Name="MenuItemsList"
Grid.Row="1"
ItemsSource="{Binding ProgramList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now when I want to catch Tap event, get properties from class and keep MVVM model. How can I catch this in MainPage.xaml.cs or in MainViewModel?
I have this code in my MainPage.xaml:
<controls:BottomMenu x:Name="BottomMenu" Canvas.Top="{Binding MenuCanvasTop}"
Width="480" Height="400">
</controls:BottomMenu>
I have prepare this code in my MainViewModel:
public RelayCommand<string> GoToSectionCommand
{
get
{
return goToArticleCommand
?? (goToArticleCommand = new RelayCommand<string>(
NavigateToSection));
}
}
But I don't know how can I call it. What's the best way?
Edit:
I tried to extend listbox:
<ListBox x:Name="MenuItemsList"
Grid.Row="1"
ItemsSource="{Binding ProgramList, Mode=OneWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Button Content="{Binding Title}"
Command="{Binding ListButtonClickCommand, Source={RelativeSource Self}}"
CommandParameter="{Binding Url}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
With code-behind:
public ICommand ListButtonClickCommand
{
get { return (ICommand)GetValue(ListButtonClickCommandProperty); }
set { SetValue(ListButtonClickCommandProperty, value); }
}
public static readonly DependencyProperty ListButtonClickCommandProperty =
DependencyProperty.Register("ListButtonClickCommand", typeof(ICommand), typeof(BottomMenu), new PropertyMetadata(null));
public BottomMenu()
{
InitializeComponent();
}
Then in MainPage.xaml:
<controls:BottomMenu x:Name="BottomMenu" Canvas.Top="{Binding MenuCanvasTop}"
Width="480" Height="400"
ListButtonClickCommand="{Binding MenuItemButtonCommand}">
</controls:BottomMenu>
And in MainViewModel:
private ICommand menuItemButtonCommand;
public ICommand MenuItemButtonCommand
{
get
{
return menuItemButtonCommand
?? (menuItemButtonCommand = new RelayCommand(
NavigateToArticleSection));
}
}
For now without luck. It's not working. RelayCommand isn't triggered.
Edit2
I guess the problem is with binding command in custom control but I don't know how to fix it.
You just need to bind to the UserControl -- using "RelativeSource Self" will bind to the Button, which is not what you want. You should be able to use an "ElementName" binding to locate the user control:
<UserControl x:Name="UserControlName" ... >
...
<Button Content="{Binding Title}"
Command="{Binding ElementName=UserControlName,Path=ListButtonClickCommand}"
CommandParameter="{Binding Url}"/>
...
</UserControl>

Multi selection in WPF Listview and multiple styles

I have an application in WPF (MVVM).
I've created a view model which points to an ObservableCollections to be shown in the ListView.
The ListView has two custom view resources. 'GridView' and a 'TileView' and buttons to switch between them.
When I select multiple items and change to the other view the selected items are not sync...
after trying to debug I think that, for some reason, when changing views it:
Gets the IsSelected value for every Item in the list (That's OK)
Sets (again..) the Items that have just been set (in the other view..) (?)
The markup:
<Window.Resources>
<local:TileView x:Key="ImageView">
<local:TileView.ItemTemplate >
<DataTemplate >
<StackPanel Width="150" VerticalAlignment="Top">
<Image Source="{Binding Path=ImagePath}"/>
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" Text="{Binding Path=PhotoReferenceCode}"/>
</StackPanel>
</DataTemplate>
</local:TileView.ItemTemplate>
</local:TileView>
<GridView x:Key ="ListView">
<GridViewColumn Header="Select" Width="100" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid Width="100">
<!--<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay}" HorizontalAlignment="Center"/>-->
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Image" Width="100" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Path=ImagePath}" Stretch="UniformToFill" HorizontalAlignment="Center" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Reference Code" Width="100" DisplayMemberBinding="{Binding Path=PhotoReferenceCode}"/>
</GridView>
</Window.Resources>
<Grid>
<ListView Margin="0,36,0,0" View="{Binding Path=ViewType, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" SelectionMode="Multiple"
ItemsSource="{Binding Path=InfoList, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
<Button Name="btnListView" Content="ListView" Click="btnListView_Click" HorizontalAlignment="Left" Margin="10,9,0,0" VerticalAlignment="Top" Width="75"/>
<Button Name="btnImageView" Content="ImageView" Click="btnImageView_Click" HorizontalAlignment="Left" Margin="99,9,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
The Item class:
public class ItemData : INotifyPropertyChanged
{
#region Members
private bool _IsSelected;
public string ImagePath { get; set; }
public string PhotoReferenceCode { get; set; }
#endregion
#region Constractors
public ItemData()
{
}
public ItemData(bool Is_Sected, string Image_Path, int Item_Carat, string Photo_Reference_Code)
{
IsSelected = Is_Sected;
ImagePath = Image_Path;
ItemCarat = Item_Carat;
PhotoReferenceCode = Photo_Reference_Code;
}
#endregion
#region Public
public bool IsSelected
{
get { return _IsSelected; }
set
{
if (this.PropertyChanged != null && _IsSelected != value)
{
_IsSelected = value; OnPropertyChanged("IsSelected");
}
}
}
#endregion
#region Events
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Any help would be appreciated.
Bind all of the listview's SelectedItems to a property in the view model using TwoWay bind. So the viewModel keeps the selectedItems of the listview and so when the view is changed, the new listview style can pick up the selectedItems from the viewModel

Categories