Binding issue with button hosted in 2 controls - c#

Hi I know this is alot of code but I'm hoping someone can help or point me in the right direction my export to csv command isn't firing even when using breakpoints on the command and task so I assume it can't be found in the data context somwhere, everything else works the data gets populated, edit button works.
So I have 2 UserControls TitleControl and BillOfMaterialsControl, I have some buttons hosted in the title control where this is hosted in the bill of materials control, it seems my buttons are not working with being hosted in another control.
I'm getting this error in the output window:
System.Windows.Data Error: 40 : BindingExpression path error: 'ExportButtonCommand' property not found on 'object' ''TitleControl' (Name='')'. BindingExpression:Path=ExportButtonCommand; DataItem='TitleControl' (Name=''); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
Any help is very much appreciated.
BillOfMaterials.xaml Control
<UserControl x:Class="Bright_Instruments.Controls.BillOfMaterialsControl"
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:Bright_Instruments.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" xmlns:controls="clr-namespace:Bright_Instruments.Controls"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<controls:TitleControl Grid.Column="0" Badge="{Binding Badge, RelativeSource={RelativeSource AncestorType=UserControl}}" Grid.Row="0" Margin="5" Text="PARTS LIST" Icon="{iconPacks:PicolIcons Kind=ListNumbered}">
<controls:TitleControl.TitleContent>
<StackPanel Margin="0" HorizontalAlignment="Right" Orientation="Horizontal">
<Button ToolTip="Print parts list" Margin="5" HorizontalAlignment="Right" Style="{DynamicResource MahApps.Styles.Button.Chromeless}">
<Button.Content>
<iconPacks:PackIconEntypo Kind="Print"/>
</Button.Content>
</Button>
<Button Margin="5"
Command="{Binding ExportButtonCommand, RelativeSource={RelativeSource AncestorType=UserControl}, UpdateSourceTrigger=PropertyChanged}" ToolTip="Export parts list to .csv" HorizontalAlignment="Right" Style="{DynamicResource MahApps.Styles.Button.Chromeless}">
<Button.Content>
<iconPacks:PackIconFontAwesome Kind="FileCsvSolid"/>
</Button.Content>
</Button>
<Button Margin="5" ToolTip="Export parts list to .pdf" HorizontalAlignment="Right" Style="{DynamicResource MahApps.Styles.Button.Chromeless}">
<Button.Content>
<iconPacks:PackIconFontAwesome Kind="FilePdfSolid"/>
</Button.Content>
</Button>
</StackPanel>
</controls:TitleControl.TitleContent>
</controls:TitleControl>
<StackPanel Orientation="Horizontal" Grid.Column="1">
<Button Margin="0, 5, 5, 5"
Command="{Binding EditButtonCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
Style="{DynamicResource MahApps.Styles.Button.Flat}"
Content="EDIT"/>
</StackPanel>
<DataGrid Grid.Row="1" AutoGenerateColumns="False" Grid.Column="0" Grid.ColumnSpan="2"
ItemsSource="{Binding BillOfMaterials, RelativeSource={RelativeSource AncestorType=UserControl}, UpdateSourceTrigger=PropertyChanged}" Margin="5" IsReadOnly="True" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=ChildItem.PartNumber}" Width="Auto">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconFontAwesome Kind="WrenchSolid" Margin="5" />
<TextBlock Margin="5" Text="PART NUMBER"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=ChildItem.Description}" Width="*">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconMaterialDesign Kind="Description" Margin="5" />
<TextBlock Margin="5" Text="DESCRIPTION"/>
</StackPanel>
</DataGridTextColumn.Header>
<DataGridTextColumn.ElementStyle>
<Style>
<Setter Property="TextBlock.TextWrapping" Value="Wrap" />
<Setter Property="TextBlock.TextAlignment" Value="Left"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=ChildItem.Location}" Width="Auto">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconMaterialDesign Kind="LocationOn" Margin="5" />
<TextBlock Margin="5" Text="LOCATION"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Path=ChildItem.Quantity}" Width="Auto">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconMaterial Kind="Numeric" Margin="5" />
<TextBlock Margin="5" Text="QUANTITY IN STOCK"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="Auto" Binding="{Binding Path=Quantity}">
<DataGridTextColumn.Header>
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconMaterial Kind="Numeric" Margin="5" />
<TextBlock Margin="5" Text="QUANTITY REQ"/>
</StackPanel>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTemplateColumn Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Margin="0" Style="{DynamicResource MahApps.Styles.Button.Flat}"
CommandParameter="{Binding }"
Command="{Binding ViewButtonCommand, RelativeSource={RelativeSource AncestorType=UserControl}}" Content="VIEW"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.Header>
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconTypicons Margin="5" Kind="Eye"/>
<TextBlock Text="VIEW" Margin="5"/>
</StackPanel>
</DataGridTemplateColumn.Header>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>
BillOfMaterials.cs Control
public partial class BillOfMaterialsControl : UserControl
{
public BillOfMaterialsControl()
{
InitializeComponent();
}
public string Badge
{
get { return (string)this.GetValue(BadgeProperty); }
set { SetValue(BadgeProperty, value); }
}
public static readonly DependencyProperty BadgeProperty = DependencyProperty.Register(
"Badge", typeof(string), typeof(BillOfMaterialsControl), new PropertyMetadata(string.Empty));
public ICommand ViewButtonCommand
{
get { return (ICommand)GetValue(ViewButtonCommandProperty); }
set { SetValue(ViewButtonCommandProperty, value); }
}
public static readonly DependencyProperty ViewButtonCommandProperty = DependencyProperty.Register(
"ViewButtonCommand", typeof(ICommand), typeof(BillOfMaterialsControl), new UIPropertyMetadata(null));
public ICommand ExportButtonCommand
{
get { return (ICommand)GetValue(ExoportButtonCommandProperty); }
set { SetValue(ExoportButtonCommandProperty, value); }
}
public static readonly DependencyProperty ExoportButtonCommandProperty = DependencyProperty.Register(
"ExportButtonCommand", typeof(ICommand), typeof(BillOfMaterialsControl), new UIPropertyMetadata(null));
public ICommand EditButtonCommand
{
get { return (ICommand)GetValue(EditButtonCommandProperty); }
set { SetValue(EditButtonCommandProperty, value); }
}
public static readonly DependencyProperty EditButtonCommandProperty = DependencyProperty.Register(
"EditButtonCommand", typeof(ICommand), typeof(BillOfMaterialsControl), new UIPropertyMetadata(null));
public List<BillOfMaterial> BillOfMaterials
{
get { return (List<BillOfMaterial>)this.GetValue(BillOfMaterialsProperty); }
set { SetValue(BillOfMaterialsProperty, value); }
}
public static readonly DependencyProperty BillOfMaterialsProperty = DependencyProperty.Register(
"BillOfMaterials", typeof(List<BillOfMaterial>), typeof(BillOfMaterialsControl), new PropertyMetadata(null));
}
TitleControl.xaml Control
<UserControl x:Class="Bright_Instruments.Controls.TitleControl"
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:Bright_Instruments.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
xmlns:Controls="http://metro.mahapps.com/winfx/xaml/controls">
<Grid Background="{DynamicResource MahApps.Brushes.Accent}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0">
<ContentPresenter Margin="5" Content="{Binding Icon}"
TextBlock.Foreground="{DynamicResource MahApps.Brushes.IdealForeground}" />
<TextBlock
Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"
FontSize="12" VerticalAlignment="Center"
HorizontalAlignment="Left" Background="Transparent"
Foreground="{DynamicResource MahApps.Brushes.IdealForeground}"
Padding="5"/>
<Controls:Badged BadgePlacementMode="TopRight" Margin="10, 0, 0, 0" Badge="{Binding Badge}">
<Controls:Badged.Style>
<Style TargetType="Controls:Badged">
<Style.Triggers>
<Trigger Property="Badge" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</Style.Triggers>
</Style>
</Controls:Badged.Style>
</Controls:Badged>
</StackPanel>
<ContentPresenter Content="{Binding TitleContent, UpdateSourceTrigger=PropertyChanged}" Grid.Column="1" Grid.Row="0"/>
<Rectangle Height="2" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Bottom" Fill="{DynamicResource MahApps.Brushes.AccentBase}">
</Rectangle>
</Grid>
</UserControl>
TitleControl.cs Control
public partial class TitleControl : UserControl
{
public TitleControl()
{
InitializeComponent();
}
public string Text
{
get { return (string)this.GetValue(TextProperty); }
set { this.SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text", typeof(string), typeof(TitleControl), new PropertyMetadata(string.Empty));
public string Badge
{
get { return (string)this.GetValue(BadgeProperty); }
set { this.SetValue(BadgeProperty, value); }
}
public static readonly DependencyProperty BadgeProperty = DependencyProperty.Register(
"Badge", typeof(string), typeof(TitleControl), new PropertyMetadata(string.Empty));
public PackIconBase Icon
{
get { return (PackIconBase)this.GetValue(IconProperty); }
set { this.SetValue(IconProperty, value); }
}
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
"Icon", typeof(PackIconBase), typeof(TitleControl), new PropertyMetadata(null));
public object TitleContent
{
get { return (object)GetValue(TitleContentProperty); }
set { SetValue(TitleContentProperty, value); }
}
public static readonly DependencyProperty TitleContentProperty = DependencyProperty.Register(
"TitleContent", typeof(object), typeof(TitleControl), new UIPropertyMetadata(null));
}
My view where the bill of materials control is hosted
<controls:BillOfMaterialsControl
EditButtonCommand="{Binding EditPartsListCommand}"
Badge="{Binding PartsListCount}"
ViewButtonCommand="{Binding ViewPartsListItemCommand}"
ExportButtonCommand="{Binding ExportPartsListToCsvCommand, UpdateSourceTrigger=PropertyChanged}"
BillOfMaterials="{Binding BillOfMaterials, UpdateSourceTrigger=PropertyChanged}"/>
Views ViewModel
public ICommand ExportPartsListToCsvCommand => new AsyncRelayCommand(ExportPartsListToCsv);
public async Task ExportPartsListToCsv()
{
var saveFileDialog = new SaveFileDialog();
var filter = $"CSV (*.csv) | *.csv";
saveFileDialog.Filter = filter;
saveFileDialog.DefaultExt = ".csv";
saveFileDialog.FileName = "Inventory.csv";
if (saveFileDialog.ShowDialog() == true)
{
try
{
await CsvService.Write<BillOfMaterial>(saveFileDialog.FileName, BillOfMaterials);
}
catch (Exception ex)
{
SentrySdk.CaptureException(ex);
}
}
}

The BillOfMaterialsControl uses a TitleControl control that in turn contains a Button that binds the ExportButtonCommand (which is a property in BillOfMaterialsControl) as command. Here, you use an relative source binding with ancestor type UserControl in it.
Command="{Binding ExportButtonCommand, RelativeSource={RelativeSource AncestorType=UserControl}, UpdateSourceTrigger=PropertyChanged}"
Picture the visual tree similiar to this (only with the essential parts in it).
BillOfMaterialsControl
TitleControl
Button
The relative source binding will search up the visual tree to find a control of type UserControl. Both of your controls derive from UserControl and the first one found is TitleControl, but this control does not contain a property called ExportButtonCommand and that is the binding failure that you get.
System.Windows.Data Error: 40 : BindingExpression path error: 'ExportButtonCommand' property not found on 'object' ''TitleControl' (Name='')'. BindingExpression:Path=ExportButtonCommand; DataItem='TitleControl' (Name=''); target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
You could solve this issue in one of these ways.
Modify the binding to use the concrete type of your control BillOfMaterialsControl as ancestor type. This way TitleControl does not match.
Command="{Binding ExportButtonCommand, RelativeSource={RelativeSource AncestorType=local:BillOfMaterialsControl}, UpdateSourceTrigger=PropertyChanged}"
Specify a suitable AncestorLevel to skip controls in the search process.
Command="{Binding ExportButtonCommand, RelativeSource={RelativeSource AncestorType=UserControl, AncestorLevel=2}, UpdateSourceTrigger=PropertyChanged}"
A value of 2 should work (if not, adapt it). From the documentation on AncestorLevel:
Gets or sets the level of ancestor to look for, in FindAncestor mode. Use 1 to indicate the one nearest to the binding target element.

Related

SplitView's Pane Content Template with MVVM

I need some help wrapping my brain around the concepts of MVVM. I must say that it's been hard to learn it so far becasue some information on the internet contradict each other. So if anyone has a reliable source of information/tutorial which can get me somewhere, I'd really appreciate it.
However, for now, I need help figuruing out how I can close the SplitView Pane in UWP app with the help of MVVM from the Content of the Pane. I have a ShellView whcih hosts the SplitView and its Pane hosts a ContentPresenter whose content can be dynamically changed to one of two Templates. The Bindng properties are within the ViewModel and so is the IsSubscriptionsPaneOpen property.
ShellView.xaml (Page):
<SplitView
Name="sv"
DisplayMode="Overlay"
OpenPaneLength="280"
IsPaneOpen="{Binding IsSubscriptionsPaneOpen, Mode=TwoWay, UpdateSourceTrigger=Default}">
<SplitView.Pane>
<ContentPresenter
Content="{Binding PaneContent, Mode=OneWay, UpdateSourceTrigger=Default}"
ContentTransitions="{StaticResource NavigationTransitions}"/>
</SplitView.Pane>
<Grid>
<Frame
x:Name="RootFrame"
ContentTransitions="{StaticResource NavigationTransitions}"/>
</Grid>
</SplitView>
Then comes one of the Templates' View which needs to be instantiated in the PaneContent property. I do it within the ShellViewModel as below:
private SubscriptionsView SubsPaneView { get; set; }
if (SubsPaneView == null) SubsPaneView = new SubscriptionsView();
PaneContent = SubsPaneView;
SubscriptionView.xaml (UserControl):
<UserControl.Resources>
<ViewModels:SubscriptionsViewModel x:Key="SubsVM"/>
<DataTemplate x:Key="UserSubscriptionsDataTemplate"
x:DataType="model:SubscriptionsItem">
<UserControl>
<Grid
Name="rootPanel">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.Background>
<ImageBrush
ImageSource="{Binding ChannelCoverUrl, Mode=OneWay, UpdateSourceTrigger=Default}"
Opacity="0.4"/>
</Grid.Background>
<Ellipse
Name="imageBorder"
Grid.Column="0"
Height="44"
Width="44"
Stroke="{StaticResource SystemControlBackgroundAccentBrush}"
StrokeThickness="1"
VerticalAlignment="Center">
<Ellipse.Fill>
<ImageBrush>
<ImageBrush.ImageSource>
<BitmapImage
UriSource="{Binding ImageUrl, Mode=OneWay}"/>
</ImageBrush.ImageSource>
</ImageBrush>
</Ellipse.Fill>
</Ellipse>
<TextBlock
Name="title"
Grid.Column="1"
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding Title, Mode=OneWay, UpdateSourceTrigger=Default}"
MaxLines="2"
TextTrimming="CharacterEllipsis"
Margin="4,0"
VerticalAlignment="Center"
ToolTipService.ToolTip="{Binding Title, Mode=OneWay}"/>
<Border
Name="newContent"
Grid.Column="2"
Background="{StaticResource SystemControlBackgroundAccentBrush}"
CornerRadius="100"
Height="36"
Width="36"
VerticalAlignment="Center"
Visibility="{Binding NewItemCount, Mode=OneWay, UpdateSourceTrigger=Default, Converter={StaticResource NumberToVisibleConverter}}">
<ToolTipService.ToolTip>
<TextBlock>
<Run Text="Updates:"/>
<Run Text="{Binding NewItemCount, Mode=OneWay, UpdateSourceTrigger=Default}"/>
</TextBlock>
</ToolTipService.ToolTip>
<TextBlock
Text="{Binding NewItemCount, Mode=OneWay, UpdateSourceTrigger=Default}"
FontSize="12"
VerticalAlignment="Center"
TextAlignment="Center"/>
</Border>
<AppBarButton
Name="subsRemoveBtn"
Grid.Column="3"
Height="40"
Width="40"
Style="{StaticResource SquareAppBarButtonStyle}"
Command="{Binding OpenUnsubscribeFlyout, Mode=OneWay, UpdateSourceTrigger=Default}"
VerticalAlignment="Center">
<AppBarButton.Icon>
<FontIcon
Glyph=""
Margin="0,-4,0,0"/>
</AppBarButton.Icon>
<FlyoutBase.AttachedFlyout>
<Flyout helpers:FlyoutHelper.IsOpen="{Binding IsFlyoutOpen, Mode=TwoWay, UpdateSourceTrigger=Default}"
helpers:FlyoutHelper.Parent="{Binding ElementName=subsRemoveBtn}">
<Button
Content="Unsubscribe"
Command="{Binding RemoveSubscription, Mode=OneWay, UpdateSourceTrigger=Default}"
CommandParameter="{Binding ID, Mode=OneWay, UpdateSourceTrigger=Default}"/>
</Flyout>
</FlyoutBase.AttachedFlyout>
<ToolTipService.ToolTip>
<TextBlock
TextWrapping="Wrap">
<Run Text="Unsubscribe"/>
<Run Text="{Binding Title, Mode=OneWay, UpdateSourceTrigger=Default}"/>
</TextBlock>
</ToolTipService.ToolTip>
</AppBarButton>
</Grid>
</UserControl>
</DataTemplate>
</UserControl.Resources>
<ListView
Name="subscriptionsList"
Grid.Row="1"
helpers:ItemClickCommand.Command="{Binding ViewChannelCommand}"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ItemTemplate="{StaticResource UserSubscriptionsDataTemplate}"
ItemsSource="{Binding SubscriptionsList, Mode=OneWay, UpdateSourceTrigger=Default}"
SelectedItem="{Binding SelectedSubscription, Mode=TwoWay, UpdateSourceTrigger=Default}"/>
At first I was closing the Pane through a readonly instance of ShellViewModel but I dont think it's purely MVVM and that is why I am looking at how to make it purely MVVM. The goal is to use the Click command of subscriptionsList to close the Pane.
public CustomICommand<SubscriptionsItem> ViewChannelCommand { get; private set; }
ViewChannelCommand = new CustomICommand<SubscriptionsItem>(ViewSubscriptionChannel, CanViewSubscriptionChannel);
private void ViewSubscriptionChannel(SubscriptionsItem channel)
{
CommandsService.ViewUserChannelForChannelID(channel.ChannelID, false);
//ShellViewModel.Instance?.IsSubscriptionsPaneOpen = false;
}
Now I don't see a way of closing the Pane through ItemTemplate of the ListView. I believe it maybe possible through Dependency Injection becasue I am using the same thing for closing the Flyouts already but I dont know how I implement it here becasue there is no documentation available. Can anyone help?
Edit:
I am using below class for ListView itemclick through MVVM pattern.
public static class ItemClickCommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(ItemClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged));
public static void SetCommand(DependencyObject d, ICommand value)
{
d.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject d)
{
return (ICommand)d.GetValue(CommandProperty);
}
private static void OnCommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = d as ListViewBase;
if (control != null)
control.ItemClick += OnItemClick;
}
private static void OnItemClick(object sender, ItemClickEventArgs e)
{
var control = sender as ListViewBase;
var command = GetCommand(control);
if (command != null && command.CanExecute(e.ClickedItem))
command.Execute(e.ClickedItem);
}
}
The reasonable thing to do here is to add another dependency property in the ItemClickCommand class above:
...
public static readonly DependencyProperty IsPanelOpenCommandProperty =
DependencyProperty.RegisterAttached("IsPanelOpenCommand", typeof(ICommand),
typeof(ItemClickCommand), null);
public static void SetIsPaneOpenCommand(DependencyObject d, ICommand value)
{
d.SetValue(IsPanelOpenCommandProperty, value);
}
public static ICommand GetIsPaneOpenCommand(DependencyObject d)
{
return (ICommand)d.GetValue(IsPanelOpenCommandProperty);
}
// in item click insert the following
var paneCommand = GetIsPaneOpenCommand(control);
if (paneCommand != null)
paneCommand.Execute();
....

Could not pass values from view to viewmodel through commands

I am implementing MVVM pattern for my application. I have my Views, ViewModel, Model and Commands and Converters being implemented. Now I am not able to pass my textboxes values from my datatemplate to my ViewModel through the command binding. I am able to click on the button to attempt the update process but its not able to pass the textboxes value. Are there something i need to change on my command class?
Here is my XAML:
<DataGrid AutoGenerateColumns="False" Grid.Row="2" Grid.ColumnSpan="4" Grid.RowSpan="3" x:Name="productionLineConfigDataGrid" Margin="70,0.2,70,0" ItemsSource="{Binding listAllProductionLineConfigs}">
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="12" Text="ID: " VerticalAlignment="Center" />
<TextBlock x:Name="txtBlockLineId" FontSize="16" Foreground="MidnightBlue" Text="{Binding ProductionLineId, Mode=TwoWay}" VerticalAlignment="Center" />
</StackPanel>
<StackPanel>
<Button x:Name="btnUpdate" Content="Update" VerticalAlignment="Center" HorizontalAlignment="Right" Click="btnUpdate_Click" Command="{Binding DataContext.updateProductionLineConfigCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:production_line_config_home}}}" CommandParameter="{Binding ProductionLineConfig}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</DataGrid>
Here is the method from my ViewModel:
public ProductionLineConfig ProductionLineConfig
{
get { return productionlineconfig; }
set
{
productionlineconfig = value;
OnPropertyChanged("ProductionLineConfig");
}
}
This is the error message i am getting:
System.Windows.Data Error: 40 : BindingExpression path error: 'ProductionLineConfig' property not found on 'object' ''ProductionLineConfig' (HashCode=47309994)'. BindingExpression:Path=ProductionLineConfig; DataItem='ProductionLineConfig' (HashCode=47309994); target element is 'Button' (Name=''); target property is 'CommandParameter' (type 'Object')
I have included the image for my application here
This is the entire xaml code here and this is the entire viewmodel code here
I'm only going to take a guess at this.
Assuming ProductionLineConfig is what you are binding to for ProductionLineId in the template. Then you probably are just wanting to pass your binding source as the command parameter
CommandParameter="{Binding}"
When {Binding} is empty, it means the Binding is bound to whatever Source there is. Which is also just shorthand for...
{Binding DataContext,RelativeSource={RelativeSource Self}}.
In turn (if the stars align), then it should be your ProductionLineConfig
Based on your code source, I made a sample implementation, to achieve your requirement.
Sample VM:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApp5.ViewModels
{
public class ProductionLineConfigViewModel : INotifyPropertyChanged
{
public CustomCommand<ProductionLineConfig> UpdateCommand { get; }
public ProductionLineConfigViewModel()
{
PopulateProductionLineConfigs();
UpdateCommand = new CustomCommand<ProductionLineConfig>(UpdateConfig, (u) => true);
}
private ObservableCollection<ProductionLineConfig> _listAllProductionLineConfigs;
public ObservableCollection<ProductionLineConfig> listAllProductionLineConfigs
{
get { return _listAllProductionLineConfigs; }
set
{
_listAllProductionLineConfigs = value;
OnPropertyChanged();
}
}
// Call this from constructor.
private void PopulateProductionLineConfigs()
{
listAllProductionLineConfigs = new ObservableCollection<ProductionLineConfig>
{
new ProductionLineConfig
{
ProductionLineId = 1,
ProductionLineCode = "001",
ProductionLineCreatedDate = DateTime.Today.Date,
ProductionLineName = "safdsf",
ProductionLineStatus = true
},
new ProductionLineConfig
{
ProductionLineId = 1,
ProductionLineCode = "002",
ProductionLineCreatedDate = DateTime.Today.Date,
ProductionLineName = "sadfadfsdf",
ProductionLineStatus = true
}
};
}
private void UpdateConfig(ProductionLineConfig config)
{
MessageBox.Show("Line Name update: " + config.ProductionLineName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ProductionLineConfig
{
public int ProductionLineId { get; set; }
public string ProductionLineCode { get; set; }
public string ProductionLineName { get; set; }
public bool ProductionLineStatus { get; set; }
public DateTime ProductionLineCreatedDate { get; set; }
}
}
Sample XAML:
<Window x:Name="Root" x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:WpfApp5.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<viewModels:ProductionLineConfigViewModel/>
</Window.DataContext>
<Grid Background="#FF006E8C">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Grid.ColumnSpan="4" Content="KAD ShopFloor System" HorizontalAlignment="Center" Margin="10" FontWeight="Bold" FontSize="30" FontFamily="Segoe UI" Foreground="White"/>
<Separator Grid.ColumnSpan="4" Grid.RowSpan="3" Background="White" Margin="0,-35,-0.4,39.2"/>
<DataGrid AutoGenerateColumns="False" Grid.Row="2" Grid.ColumnSpan="4" Grid.RowSpan="3" x:Name="productionLineConfigDataGrid" Margin="70,0.2,70,0"
ItemsSource="{Binding DataContext.listAllProductionLineConfigs, ElementName=Root}">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ProductionLineId, Mode=TwoWay}"/>
<DataGridTextColumn Header="Production Line Code" Binding="{Binding ProductionLineCode, Mode=TwoWay}"/>
<DataGridTextColumn Header="Production Line Name" Binding="{Binding ProductionLineName, Mode=TwoWay}"/>
<DataGridTextColumn Header="Status" Binding="{Binding ProductionLineStatus, Mode=TwoWay}"/>
<DataGridTextColumn Header="Created Date" Binding="{Binding ProductionLineCreatedDate, Mode=TwoWay}"/>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Border BorderThickness="0" Background="BlanchedAlmond" Padding="10">
<StackPanel Orientation="Vertical" x:Name="stck">
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="12" Text="ID: " VerticalAlignment="Center" />
<TextBlock x:Name="txtBlockLineId" FontSize="16" Foreground="MidnightBlue" Text="{Binding ProductionLineId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="12" Text="Line Code: " VerticalAlignment="Center" />
<TextBlock x:Name="txtBlockLineCode" FontSize="16" Foreground="MidnightBlue" Text="{Binding ProductionLineCode, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="12" Text="Line Name: " VerticalAlignment="Center" />
<TextBox x:Name="txtLineName" FontSize="16" Foreground="MidnightBlue" Text="{Binding ProductionLineName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" />
</StackPanel>
<!--<StackPanel Orientation="Horizontal">
<TextBlock FontSize="12" Text="Status: " VerticalAlignment="Center" />
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGrid}},
Path=DataContext.Statusstring}" SelectedValue="{Binding ProductionLineStatus, Converter={StaticResource statusToBooleanConverter}, Mode=TwoWay}" x:Name="cbProductionLineStatus" FlowDirection="LeftToRight" FontSize="16" Foreground="MidnightBlue"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
</StackPanel>-->
<StackPanel>
<Button x:Name="btnUpdate" Content="Update" VerticalAlignment="Center" HorizontalAlignment="Right"
Command="{Binding DataContext.UpdateCommand, ElementName=Root}"
CommandParameter="{Binding}" />
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
</Window>
Sample output:
Key changes here,
You need to change your list to observable collection
Create a custom command that accepts an object, see this post: [UWP/MVVM]Enable/Disable Button in RadDataGrid Data Template Column that have commands bound to them upon conditions
Command Parameter should be the iterated item, can be achieve by CommandParameter={Binding}
In you two way bindings, make sure to add UpdateSourceTrigger=PropertyChanged

Custom UserControl Template with Property-Binding

I created an custom UserControl which includes an Ellipse and a Label. The Label Content and the Color of the Ellipse is binded:
The Binding-Properties are DependencyProperties.
The color of the Ellipse is depended on the Status, which is a bool (a converter creates the Color)
This is the UserControl1.xaml:
<UserControl x:Class="Plugin_UPS.Views.UserControl1"
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:Converters="clr-namespace:WPF_Prism.Infrastructure.Converters.Ups;assembly=WPF_Prism.Infrastructure"
mc:Ignorable="d"
x:Name="UC1"
d:DesignWidth="200" Height="40">
<UserControl.Resources>
<ResourceDictionary>
<Converters:BoolToColorConverter x:Key="BoolToColorConverter"/>
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Height="42" Width="504">
<StackPanel HorizontalAlignment="Left" Margin="0,0,0,0" Height="Auto" Width="Auto" Grid.Column="0" Grid.Row="0">
<Grid Height="Auto" Width="Auto" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,0,0"
MinWidth="200" MaxWidth="200" MinHeight="35" MaxHeight="40">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse HorizontalAlignment="Left" VerticalAlignment="Center" Grid.Column="0" Grid.Row="0"
Fill="{Binding Status, Converter={StaticResource BoolToColorConverter}, UpdateSourceTrigger=PropertyChanged}"
Height="15"
Width="15"
StrokeThickness="1"
Stroke="Black">
</Ellipse>
<Label Content="{Binding Text}" Height="Auto" Width="Auto" Margin="0,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="16" Grid.Column="1" Grid.Row="0" />
</Grid>
</StackPanel>
</Grid>
</UserControl>
This is the UserControl1.xaml.cs:
namespace Plugin_UPS.Views
{
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(UserControl1));
public bool Status
{
get { return (bool)GetValue(StatusProperty); }
set { SetValue(StatusProperty, value); }
}
public static readonly DependencyProperty StatusProperty =
DependencyProperty.Register("Status", typeof(bool), typeof(UserControl1));
}
}
This is my UPSViewModel.cs:
namespace Plugin_UPS.ViewModel
{
public class UPSViewModel
{
public UPSViewModel()
{
}
public string UpsModelText { get { return "Model"; } }
private bool replaceBatteryCondition;
public bool ReplaceBatteryCondition { get { return replaceBatteryCondition; } set { replaceBatteryCondition = value; } }
}
}
This is my UPSView.xaml:
(here I implement the UserControl1)
<UserControl x:Class="Plugin_UPS.Views.UPSView"
x:Name="UPSUC"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:Plugin_UPS"
xmlns:Controls="clr-namespace:WPF_Prism.Controls.Controls;assembly=WPF_Prism.Controls"
xmlns:ViewModel="clr-namespace:Plugin_UPS.ViewModel"
xmlns:Views="clr-namespace:Plugin_UPS.Views"
d:DataContext="{d:DesignInstance ViewModel:UPSViewModel}"
mc:Ignorable="d"
d:DesignHeight="840" d:DesignWidth="1260">
<Grid>
<StackPanel>
<Views:UserControl1 Margin="10,20,10,10" DataContext="{Binding RelativeSource={RelativeSource Self}}" Text="{Binding UpsModelText}" Status="{Binding ReplaceBatteryCondition}"></Views:UserControl1>
</StackPanel>
</Grid>
</UserControl>
But the binding for "Status" and "Text" is not working.
(Status="True" or Text="Model" is working).
Do you have any ideas what the problem could be?
Is there a problem with the DataContext?
best regards
Phil
You must not set the UserControl's DataContext to itself, as done by
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Remove that assignment and write
<Views:UserControl1 Margin="10,20,10,10"
Text="{Binding UpsModelText}"
Status="{Binding ReplaceBatteryCondition}" />
Now you'll have to specify the source of the bindings in the UserControl's XAML.
There are multiple ways to do that, one of them is to set the RelativeSource property like this:
<Label Content="{Binding Text,
RelativeSource={RelativeSource AncestorType=UserControl}}" ... />
<Ellipse Fill="{Binding Status,
RelativeSource={RelativeSource AncestorType=UserControl},
Converter={StaticResource BoolToColorConverter}}" ... />
Note that setting UpdateSourceTrigger=PropertyChanged on the Fill Binding doesn't make sense.

DataContext passthrough with ContentProperty

I would like to create a custom control that simplifies the following code:
<StackPanel>
<DockPanel LastChildFill="True">
<Label>First Name</Label>
<TextBox Margin="2" Text="{Binding Path=FirstName}"></TextBox>
</DockPanel>
<DockPanel LastChildFill="True">
<Label>Last Name</Label>
<TextBox Margin="2" Text="{Binding Path=LastName}"></TextBox>
</DockPanel>
</StackPanel>
My thoughts was to make a UserControl like the following, (Layout is a little bit different, but thats out of scope):
<UserControl x:Class="LabelControl"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<DockPanel LastChildFill="True">
<Label Content="{Binding Path=Text}" Margin="2" MinWidth="100" HorizontalContentAlignment="Right"></Label>
<Grid Margin="2">
<ContentControl Content="{Binding Path=Control}" ></ContentControl>
</Grid>
</DockPanel>
</UserControl>
The code behind exposes 2 Dependency properties:
Text: the content of the label
Control: the control to be hosted by the content.
The class uses the ContentProperty attribute to map the children to the ContentControl.
Thus allowing me to simplify my StackPanel:
<StackPanel>
<controls:LabelControl Text="First Name">
<TextBox Text="{Binding Path=FirstName}"></TextBox>
</controls:LabelControl>
<controls:LabelControl Text="Last Name">
<TextBox Text="{Binding Path=LastName}"></TextBox>
</controls:LabelControl>
</StackPanel>
The problem I am running in to is the bindings in the the control are not mapping. Is there any way around this? The Label Controls DataContext is overridding the parent controls context.
Here is the code behind for the LabelControl:
[ContentProperty("Control")]
public partial class LabelControl : UserControl
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text", typeof(string), typeof(LabelControl), new PropertyMetadata(default(string)));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty ControlProperty =
DependencyProperty.Register("Control", typeof(Control), typeof(LabelControl), new PropertyMetadata(default(Control)));
public Control Control
{
get { return (Control)GetValue(ControlProperty); }
set { SetValue(ControlProperty, value); }
}
public LabelControl()
{
InitializeComponent();
this.DataContext = this;
}
}
Edit: Output confirms the datacontext is overriding.
BindingExpression path error: 'FirstName' property not found on 'object' ''LabelControl' (Name='')'. BindingExpression:Path=FirstName; DataItem='LabelControl' (Name=''); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
Try changing your binding like this if your LabelControl is contained within Window and it's DataContext has the FirstName property.
<TextBox Text="{Binding Path=FirstName,
RelativeSource={RelativeSource AncestorType=Window}}">
</TextBox>
If you don't want to specify RelativeSource every time, you could use your LabelControl as you do now...
<StackPanel>
<controls:LabelControl Text="First Name">
<TextBox Text="{Binding Path=FirstName}"></TextBox>
</controls:LabelControl>
<controls:LabelControl Text="Last Name">
<TextBox Text="{Binding Path=LastName}"></TextBox>
</controls:LabelControl>
</StackPanel>
...and change the LabelControl's implementation instead.
First, loose the DataContext assignment from the LabelControl's codebehind.
public LabelControl()
{
InitializeComponent();
//this.DataContext = this; // we don't want this
}
Then change the XAML template as
<DockPanel LastChildFill="True">
<Label Content="{Binding Path=Text,
RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}"
Margin="2" MinWidth="100" HorizontalContentAlignment="Right">
</Label>
<Grid Margin="2">
<ContentControl
Content="{Binding Path=Control,
RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl}}">
</ContentControl>
</Grid>
</DockPanel>
Now you should have your DataContext set up right.
I found that using UserControl was not the most ideal solution. It turns out that a templated control allows for the DataBinds to pass through without any hackery (RelativeSource).
[ContentProperty("Control")]
public class LabelControl : Control
{
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text",
typeof(string), typeof(LabelControl), new PropertyMetadata(default(string)));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty ControlProperty =
DependencyProperty.Register("Control", typeof(Control), typeof(LabelControl), new PropertyMetadata(default(Control)));
public Control Control
{
get { return (Control)GetValue(ControlProperty); }
set { SetValue(ControlProperty, value); }
}
}
In app.xaml:
<Style TargetType="controls:LabelControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:LabelControl">
<DockPanel LastChildFill="True">
<Label Content="{TemplateBinding Text}" MinWidth="100" FontSize="11"></Label>
<Grid Margin="2">
<ContentControl Content="{TemplateBinding Control}"></ContentControl>
</Grid>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

How to Attach Command to Event in WPF Using MVVM - DataGrid Selection Changed to Update the RowCount Displayed in a TextBlock

In order to attempt to get around the problem I outline in Binding Selected RowCount to TextBlock not Firing OnPropertyChanged after DataGrid Scroll; namely updating a TextBlock with the currently selected row count of a DataGrid when it scrolls. I have attempted to introduce the AttachedCommandBehaviour designed by Marlon Grech [an amazing class structure that allows you to bind commands to specific events of a given control].
Now for the question, using this AttachedCommandBehaviour, how can I get a TextBlock to update based upon a DataGrid's SelectionChangedProperty?
The XMAL is
<Window x:Class="ResourceStudio.Views.AddCultureWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:ResourceStudio.ViewModels"
xmlns:converters="clr-namespace:ResourceStudio.Converters"
xmlns:dataAccess="clr-namespace:ResourceStudio.DataAccess"
xmlns:attachedCommand="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
Title="Add Culture"
Height="510" Width="400"
WindowStartupLocation="CenterOwner"
VerticalContentAlignment="Stretch"
MinWidth="380" MinHeight="295">
<DockPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15*"/>
<ColumnDefinition Width="377*"/>
<ColumnDefinition Width="15*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="1">
<Grid>
<Grid.Resources>
<converters:EnumToBooleanConverter x:Key="enumToBooleanConverter"/>
<Style x:Key="HyperlinkButton" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<GroupBox Header="Filters" Grid.Row="0" Margin="0,0,0,5">
<StackPanel VerticalAlignment="Top">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="2,2,2,2">
<RadioButton Content="All Cultures" Margin="10,5,10,5"
HorizontalAlignment="Left"
IsChecked="{Binding SelectedFilterType,
Converter={StaticResource enumToBooleanConverter},
ConverterParameter=AllCultures}"/>
<RadioButton Content="Neutral Cultures" Margin="10,5,10,5"
HorizontalAlignment="Left"
IsChecked="{Binding SelectedFilterType,
Converter={StaticResource enumToBooleanConverter},
ConverterParameter=NeutralCultures}"/>
<RadioButton Content="Specific Cultures" Margin="10,5,10,5"
HorizontalAlignment="Left"
IsChecked="{Binding SelectedFilterType,
Converter={StaticResource enumToBooleanConverter},
ConverterParameter=SpecificCultures}"/>
</StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="Language:" Grid.Column="0"/>
<TextBox HorizontalAlignment="Stretch" Grid.Column="1"
Margin="2,0,2,0" Height="22"/>
</Grid>
</StackPanel>
</GroupBox>
<DataGrid x:Name="cultureDataGrid" Grid.Row="1" AlternatingRowBackground="Gainsboro" AlternationCount="2"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
AutoGenerateColumns="False" RowHeaderWidth="0" IsReadOnly="True"
CanUserAddRows="False" CanUserDeleteRows="False" SelectionMode="Extended"
EnableRowVirtualization="True" EnableColumnVirtualization="True"
ItemsSource="{Binding Cultures, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
<DataGrid.Columns>
<DataGridTextColumn Header="Code" Binding="{Binding Code}" IsReadOnly="True"/>
<DataGridTextColumn Header="Language" Binding="{Binding Language}" IsReadOnly="True"/>
<DataGridTextColumn Header="LocalName" Binding="{Binding LocalName}" IsReadOnly="True"/>
</DataGrid.Columns>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#FF007ACC"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, IsAsync=True}" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
<StackPanel Grid.Row="2" HorizontalAlignment="Right">
<Button Name="button1" Style="{StaticResource HyperlinkButton}"
Focusable="False">
<TextBlock>
<Hyperlink Focusable="False">
Select All
</Hyperlink>
</TextBlock>
</Button>
</StackPanel>
<GroupBox Grid.Row="3" Header="Options">
<CheckBox Content="Copy default values" Margin="3,3"/>
</GroupBox>
<StackPanel Grid.Row="4" Orientation="Horizontal"
HorizontalAlignment="Right" Margin="0,2,0,2">
<Button Content="Select" Width="70" Height="25"
Margin="0,5,5,5" HorizontalAlignment="Right"
VerticalContentAlignment="Center" IsDefault="True"/>
<Button Content="Cancel" Width="70" Height="25"
Margin="5,5,0,5" HorizontalAlignment="Right"
VerticalContentAlignment="Center" IsCancel="True"/>
</StackPanel>
</Grid>
</Grid>
</Grid>
<StatusBar Grid.Row="1" Margin="0,0.4,0.4,-0.4">
<StatusBarItem DockPanel.Dock="Left" Background="#FF007ACC" Margin="0,2,0,0">
<TextBlock Text="{Binding TotalSelectedCultures}" Margin="5,0,0,0" Foreground="White"/>
</StatusBarItem>
</StatusBar>
</Grid>
</DockPanel>
</Window>
My ViewModel is
public class CultureDataViewModel : ViewModelBase
{
public FilterType SelectedFilterType { get; private set; }
public ICollectionView CulturesView { get; private set; }
public MultiSelectCollectionView<CultureViewModel> Cultures { get; private set; }
public CultureDataViewModel()
{
SelectedFilterType = FilterType.AllCultures;
LoadCultures();
}
void OnCultureViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
string IsSelected = "IsSelected";
(sender as CultureViewModel).VerifyPropertyName(IsSelected);
if (e.PropertyName == IsSelected)
this.OnPropertyChanged("TotalSelectedCultures");
}
void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count != 0)
foreach (CultureViewModel cultVm in e.NewItems)
cultVm.PropertyChanged += this.OnCultureViewModelPropertyChanged;
if (e.OldItems != null && e.OldItems.Count != 0)
foreach (CultureViewModel cultVm in e.OldItems)
cultVm.PropertyChanged -= this.OnCultureViewModelPropertyChanged;
}
public void LoadCultures()
{
// Fill the Culutres collection...
}
public string TotalSelectedCultures
{
get
{
int selectedCultures = this.Cultures.SelectedItems.Count;
return String.Format("{0:n0} of {1:n0} cultures selected",
selectedCultures,
Cultures.Count);
}
}
}
The link to a downloadable example of the use of the AttachedCommandBehaviour is Here. Your help is most appreciated...
So, as far as I understand, you want to have TextBlock which says:
"5 of 100 items selected"
I've reconstructed your window, the button below the list shows how many items you have selected.
Is that correct? If that is really all you want to do, you don't need to involve the ViewModel at all:
All I did, was this:
<Button Content="{Binding SelectedItems.Count, ElementName=cultureDataGrid}" />
Does this help in some way already or is there a reason for using the pretty complex method with event to command etc.?
EDIT
Your problem basically melts down to "How can I bind to SelectedItems property of a DataGrid. Here is how I solved it:
Define a behavior with a dependency property for the DataGrid, which relays the SelectedItems of the DataGrid to the DependencyProperty:
public class BindableSelectedItems : Behavior<DataGrid>
{
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof (IList), typeof (BindableSelectedItems), new PropertyMetadata(default(IList), OnSelectedItemsChanged));
private static void OnSelectedItemsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var grid = ((BindableSelectedItems) sender).AssociatedObject;
if (grid == null) return;
// Add logic to select items in grid
}
public IList SelectedItems
{
get { return (IList) GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var grid = (DataGrid) sender;
SelectedItems = grid.SelectedItems;
}
}
Attach this behavior to the DataGrid and bind the SelectedItems property to a property on your ViewModel:
<DataGrid x:Name="cultureDataGrid" ItemsSource="{Binding Cultures}">
<i:Interaction.Behaviors>
<behaviors:BindableSelectedItems x:Name="CulturesSelection"
SelectedItems="{Binding SelectedCultures, Mode=OneWayToSource}"/>
</i:Interaction.Behaviors>
with the property on your ViewModel like this:
public IList SelectedCultures
{
get { return _selectedCultures; }
set
{
_selectedCultures = value;
OnPropertyChanged("SelectedCultures");
}
}
If you only want to get the count of selected items, you can bind to the behavior directly and don't need a field on the ViewModel for this:
<TextBlock Text="{Binding Path=SelectedItems.Count, ElementName=CulturesSelection}" Margin="5,0,0,0" Foreground="White"/>
In your ViewModel you can use the SelectedItems property to work with the selection:
var selectedCultures= SelectedCultures.OfType<CultureViewModel>();
I've uploaded the solution, see the link in the comments.
I hope this helps and whish you good luck! There always are a thousand ways to do stuff in WPF and it is really hard to find the best one sometimes...

Categories

Resources