Converted values from related entities are not refreshed after editing the property - wpf

I am using .net core 3.1 in VS2019. I have a user control containing a ListView listing a set of items of a price list, each of them has a Cost, Markup (percent), a discount (percent) given by a related entity called Promotion and the SalePrice, FinalPrice and PromoDescription fields computed by converters. For example, FinalPrice equals SalePrice if the item does not have a promotion, or it is (SalePrice - SalePrice*item.Promotion.Discount/100.0) otherwise.
BTW, all of these entities belong to a EF Core model with lazy loading with proxies, and all the collections are ObervableCollection, which are loaded like the following:
var dbset = Context.Promos;
dbset.Load();
return dbset.Local.ToObservableCollection();
Everything works fine except when I change the Markup or the Promotion properties, which lead to recalculating the values provided by the converters and involving none or one related entity of type Promo. Debugging the windows I can see all the changes applied correctly in the underlying view model before and after they are confirmed to the database, however I have to force it to refresh the values as in the following picture.
This is the final composition of the window:
Window
TabControl
TabItem (n)
PriceListUserControl (my user control)
Here it is the model:
Here is the wpf of the user control containing the listview:
<UserControl x:Class="MyApp.View.UserControls.PriceListUserControl"
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:MyApp.View.UserControls"
xmlns:localModel="clr-namespace:MyApp.Model"
xmlns:localVm="clr-namespace:MyApp.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="UserControlPriceList">
<UserControl.Resources>
<localVm:PriceListItemConverter x:Key="priceListItemConverter"/>
<localVm:DateConverter x:Key="dateConverter"/>
<localVm:PromoConverter x:Key="promoConverter" />
<Border Style="{StaticResource BorderStyle}" Name="bottomBorder">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="106" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Producto:</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Costo:</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Markup (%):</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Precio de Lista:</TextBlock>
<TextBlock Grid.Row="4" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Promoción:</TextBlock>
<TextBlock Grid.Row="5" Grid.Column="0" Style="{StaticResource SmallTitleStyle}" Margin="5"> Precio Final:</TextBlock>
<TextBlock Name="ProductDescription" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=Product.Description}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
<TextBlock Name="CostPrice" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=Product.CostPrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
<TextBox Name="MarkupPriceEntryForm" AutomationProperties.Name="Markup" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5" IsReadOnly="{Binding ElementName=UserControlPriceList, Path=DataContext.IsReadOnly}" >
<TextBox.Text>
<Binding Path="Markup" StringFormat="N2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Name="SalePrice" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=SalePrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
<TextBlock Name="Promotion" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=PromoFriendlyDescription}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5"
Visibility="{Binding ElementName=UserControlPriceList, Path=DataContext.HideIfEditing}"/>
<ComboBox Name="PromosComboBox" AutomationProperties.Name="PromoDiscount" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left"
Text="(Seleccione promo)"
ItemsSource="{Binding ElementName=UserControlPriceList, Path=DataContext.PromoCollection}"
SelectedValue="{Binding ElementName=UserControlPriceList, Path=DataContext.Current.PromoId, Mode=TwoWay}"
SelectedValuePath="Id"
Style="{StaticResource ComboBoxStyle}"
ItemContainerStyle="{StaticResource ComboBoxItemStyle}" Margin="8,5,0,5"
IsTextSearchEnabled="True"
IsTextSearchCaseSensitive="False"
IsDropDownOpen="False"
StaysOpenOnEdit="True"
IsEditable="True" IsReadOnly="False"
Visibility="{Binding ElementName=UserControlPriceList, Path=DataContext.ShowIfEditing}"
Width="{Binding ElementName=Promotion, Path=Width}">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type localModel:Promo}">
<TextBlock Text="{Binding Converter={StaticResource promoConverter}, ConverterParameter=PromoFriendlyDescription}"/>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>
<TextBlock Name="FinalPrice" Grid.Row="5" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=FinalPrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
</Grid>
</Border>
</DataTemplate>
</UserControl.Resources>
<Border Padding="20">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource MediumTitleStyle}" Margin="5"
Text="{Binding Path=List.Name, StringFormat='Lista: {0}'}" />
<TextBlock Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" Style="{StaticResource MediumTitleStyle}" Margin="5" HorizontalAlignment="Right"
Text="{Binding Path=List.PrintDate, StringFormat='Última impresión: {0:dd/MM/yyyy}'}" />
<Border Grid.Row="1" Style="{StaticResource BorderStyle}" Grid.ColumnSpan="4">
<ListView Name="ListViewPriceListItems" Grid.Row="1" Grid.ColumnSpan="4"
ItemsSource="{Binding Path=Items}"
IsSynchronizedWithCurrentItem="True"
SelectionChanged="OnPriceListItemSelectionChanged"
MouseDoubleClick="EnterPriceListItemEditMode"
IsEnabled="{Binding Path=IsReadOnly}">
<ListView.View>
<GridView AllowsColumnReorder="true" ColumnHeaderToolTip="Detalle de productos, precios finales y promociones">
<GridViewColumn DisplayMemberBinding="{Binding Path=Product.Description}" Header="Producto" Width="300"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Product.CostPrice, StringFormat=C}" Header="Costo"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Markup, StringFormat=N2}" Header="Markup (%)" />
<GridViewColumn DisplayMemberBinding="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=SalePrice, StringFormat=C}" Header="Precio Lista"/>
<GridViewColumn DisplayMemberBinding="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=PromoFriendlyDescription}" Header="Promoción" Width="200" />
<GridViewColumn DisplayMemberBinding="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=FinalPrice, StringFormat=C}" Header="Precio Final" />
</GridView>
</ListView.View>
</ListView>
</Border>
<ContentControl Name="PriceListDetail" Grid.Row="2" Grid.ColumnSpan="4"
Content="{Binding Path=Current}"
ContentTemplate="{StaticResource PriceListItemDetailTemplate}"
Margin="9,0,0,0" />
<StackPanel Grid.Row="3" Grid.ColumnSpan="4" Orientation="Horizontal" HorizontalAlignment="Right">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="0,0,0,0"/>
</Style>
</StackPanel.Resources>
<Button Name="EditButton" HorizontalAlignment="Right" Content="_Modificar" Style="{StaticResource AcctionButtonStyle}"
ToolTip="Editar precio y promoción del producto seleccionado en esta lista."
Click="EditSelectedPriceListItem" Visibility="{Binding Path=HideIfEditing}"/>
<Button Name="SaveButton" HorizontalAlignment="Right" Content="_Aceptar" Style="{StaticResource ActionButtonOKStyle}"
ToolTip="Guardar los cambios realizados."
Click="SavePriceListItem" Visibility="{Binding Path=ShowIfEditing}"/>
<Button Name="CancelButton" HorizontalAlignment="Right" Content="_Cancelar" Style="{StaticResource ActionButtonCancelStyle}"
ToolTip="Deshacer los cambios realizados."
Click="UndoPriceListItem" Visibility="{Binding Path=ShowIfEditing}"/>
</StackPanel>
</Grid>
</Border>
This is its view model:
public partial class PriceListItemsViewModel: BaseViewModel<PriceListItem>
{
private PriceList _list;
public PriceListItemsViewModel(PriceList list, ObservableCollection<Promo> promoCollection)
{
this.List = list ?? throw new ArgumentNullException(nameof(list));
this.PromoCollection = promoCollection;
this.Items = list.Products;
this.IsEditMode = false;
this.IsNew = false;
this.Current = this.Items.FirstOrDefault();
}
public PriceList List { get => this._list; set => this._list = value; }
public ObservableCollection<Promo> PromoCollection { get; }
}
Note: BaseViewModel has an ObservableCollection, implements Generic INotifyPropertyChange, and common properties to indicate the current selected item in that collection and other useful states
And here the wpf of the view containing the previous user control:
<Window.Resources>
<uc:PriceListUserControl x:Key="userControlPList"/>
</Window.Resources>
<Border Padding="20">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl x:Name="TabControlPriceLists" Grid.Row="1" Grid.ColumnSpan="3" TabStripPlacement="Top"
ItemsSource="{Binding Items}"
SelectedItem="{Binding Current}"
IsEnabled="{Binding IsReadOnly}" >
<TabControl.Resources>
<DataTemplate DataType="{x:Type localVm:PriceListItemsViewModel}">
<uc:PriceListUserControl x:Name="UserControlPriceList"/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding List.Name}" />
<Setter Property="MaxWidth" Value="100" />
<Setter Property="ToolTip" Value="{Binding List.Name}" />
<Setter Property="Background" Value="Bisque" />
<Setter Property="FontWeight" Value="DemiBold" />
<EventSetter Event="MouseDoubleClick" Handler="OnTabItemDoubleClick"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Border>
And its view model:
public class PriceListsViewModel : BaseViewModel<PriceListItemsViewModel>
{
private ObservableCollection<Promo> promoCollection;
public PriceListsViewModel()
{
var promoDefaultItems = new List<Promo>
{
new Promo { FriendlyName = PromoConverter.PromoFriendlyNameNoPromo, DiscountPct = 0.0F } //Workaround: Adds "(No promo) to allow the user to left an item without promotion
//new Promo { FriendlyName = PromoConverter.PromoFriendlyNameNewPromo, DiscountPct = 0F }
};
this.promoCollection = new ObservableCollection<Promo>(promoDefaultItems);
var promos = PromoRepository.PromoGetAll();
foreach (var promo in promos)
{
this.promoCollection.Add(promo);
}
var lists = PriceListRepository.PriceListGetAll();
this.Items = new ObservableCollection<PriceListItemsViewModel>();
foreach (var l in lists)
{
this.Items.Add(new PriceListItemsViewModel(l, promoCollection));
}
this.IsEditMode = false;
this.IsNew = false;
this.Current = this.Items.FirstOrDefault();
}
}

I resolved the problem by creating a new view model wrapping the former PriceListItem and providing real properties and notifying their changes.
This is the new view model (only relevant code to show the new properties)
public class PriceListItemViewModel : SupervisedEntity
{
private PriceListItem _item;
public PriceListItemViewModel(PriceListItem item)
{
this._item = item ?? throw new ArgumentNullException(nameof(item));
//Bind the parent properties to its dependants (actually they are computed properties without setter) so they notify property changes when their parent property changes.
this.AddDependentProperty(nameof(this.PromoId), nameof(this.SalePrice));
this.AddDependentProperty(nameof(this.PromoId), nameof(this.FinalPrice));
this.AddDependentProperty(nameof(this.PromoId), nameof(this.PromoFriendlyDescription));
this.AddDependentProperty(nameof(this.Markup), nameof(this.SalePrice));
this.AddDependentProperty(nameof(this.Markup), nameof(this.FinalPrice));
}
// The properties and code not related to the solution were removed for clarity
public int? PromoId
{
get => this._item.PromoId;
set
{
this._item.PromoId = (value is null || value == 0) ? null : value;
this.NotifyPropertyChanged(); //This notifies not only the change in PromoId but also in all its dependent properties
}
}
public string ProductDescription => this._item.Product.Description;
public float CostPrice => this._item.Product.CostPrice;
[Required]
public float Markup
{
get => this._item.Markup;
set
{
this._item.Markup = value;
this.NotifyPropertyChanged();
}
}
//Using properties instead of extension methods allows me to create notification for them as dependent
public float SalePrice => this._item.SalePrice();
public float FinalPrice => this._item.FinalPrice();
public int Id => this.Item.Id;
public string PromoFriendlyDescription => this._item.PromoId is null ? string.Empty : this._item.Promotion.ToString();
}
And in the xaml of the user control I replaced all the converters, which didn't get notified when one of the properties they depended on changed, by the actual properties in the new view model.
First, replacing the class of the model by the new view model, like this:
<DataTemplate x:Key="PriceListItemDetailTemplate" DataType="{x:Type localVm:PriceListItemViewModel}">
And then replacing all the references to converters in the properties that change:
<TextBlock Name="SalePrice" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=SalePrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
<TextBlock Name="FinalPrice" Grid.Row="5" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Converter={StaticResource priceListItemConverter}, ConverterParameter=FinalPrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
By the real properties in the new view model:
<TextBlock Name="SalePrice" Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=SalePrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
<TextBlock Name="FinalPrice" Grid.Row="5" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=FinalPrice, StringFormat=C}"
Style="{StaticResource TextStyleTextBlockTextBoxLike}" Margin="8,5,0,5" />
And now it works.

Related

WPF MVVM Combobox SelectedItem not working properly

I've already searched and read from several posts but I couldn't find what I am doing wrong.
I have a ComboBox with an ObservableCollection<string> Available_COMPorts as ItemsSource.
On SelectedValue I binded a string named SelectedCOMPort with Mode = TwoWay.
<Label Content="Porta COM: "></Label>
<ComboBox ItemsSource="{Binding Available_COMPorts}"
SelectedValue="{Binding SelectedCOMPort, Mode=TwoWay}" />
After bofere the combobox, I have a Label displaying the SelectedCOMPort
<Label Content="Status: " />
<Label Content="{Binding SelectedCOMPort}" Foreground="Red" />
I have made both Available_COMPorts and SelectedCOMPort with INotifyPropertyChanged. On my ViewModel Initialization, I filled the Available_COMPorts with the three SerialPort strings available ("COM6", "COM5", "COM4") and set SelectedCOMPort = "Available_COMPorts[0]" ("COM6").
When I Run the code, ComboBox has the three itens, the selected item is "COM6" and the label shows "COM6" (everything is fine). Then I select "COM5" and the label updates it's value to "COM5". This is presented on the following pictures.
The problem is when I try to access SelectedCOMPort on the ViewModel, as I need the selected item to connect on my SerialPort. The SelectedCOMPort is always as the default value "COM6". I try to access the SelectedCOMPorton the connect click command.
Debbuging, using Breakpoints on INotifyProperty functions I realized that the binding property seems to be working fine, but when it leaves INotifyProperty the value goes back to default.
Why is that happening? I've tryed several approachs and none worked for me.
Following is my ViewModel and BaseViewModel with INotifyProperty:
BaseViewModel : INotifyPropertyChanged
public class BaseViewModel : INotifyPropertyChanged
{
#region PropertyChange
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
protected void SetProperty<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, value)) return;
backingField = value;
OnPropertyChanged(propertyName);
}
#endregion
}
ViewModel
public class LiveGraphViewModel : BaseViewModel
{
public ConnectButtonCommand ConnectButtonCommand { get; set; }
private ObservableCollection<string> _availableCOMPorts;
private string _selectedCOMPort;
public ObservableCollection<string> Available_COMPorts
{
get { return _availableCOMPorts; }
set { SetProperty(ref _availableCOMPorts, value); }
}
public string SelectedCOMPort
{
get { return _selectedCOMPort; }
set { SetProperty(ref _selectedCOMPort, value); }
}
public LiveGraphViewModel()
{
this.ConnectButtonCommand = new ConnectButtonCommand(this);
ObservableCollection<string> TempCOM = new ObservableCollection<string>();
foreach (string comport in SerialPort.GetPortNames())
TempCOM.Add(comport);
Available_COMPorts = TempCOM;
if(Available_COMPorts.Count > 0)
SelectedCOMPort = Available_COMPorts[0];
}
public void ConnectButton()
{
if (SelectedCOMPort == "COM5")
Connect(SelectedCOMPort);
}
}
LiveGraphView XAML
<UserControl x:Class="SmartAthleticsWPF.Views.LiveGraphView"
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:SmartAthleticsWPF.Views"
xmlns:viewmodels="clr-namespace:SmartAthleticsWPF.ViewModels"
xmlns:commands="clr-namespace:SmartAthleticsWPF.Commands"
xmlns:syncfusion="clr-namespace:Syncfusion.UI.Xaml.Charts;assembly=Syncfusion.SfChart.WPF"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<viewmodels:LiveGraphViewModel x:Key="LIVviewModel"/>
</UserControl.Resources>
<Grid Background="#EDEDED">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.01*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="0.01*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.1*"/>
<RowDefinition Height="1.2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="0.1*"/>
</Grid.RowDefinitions>
<!--LIVE GRAPH BORDER-->
<Border Grid.Row="1" Grid.RowSpan="2"
Grid.Column="1" Grid.ColumnSpan="2"
Margin="5"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="5"
Padding="10,10,30,10"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<DockPanel>
<Label Content="Live Graph"
DockPanel.Dock="Top"
HorizontalAlignment="Center"
Padding="0,5,0,10" FontSize="22"/>
<syncfusion:SfChart x:Name="LiveGraphChart" >
<syncfusion:SfChart.PrimaryAxis>
<syncfusion:NumericalAxis Header="Seconds"
Maximum="{Binding MaxXAxis}"
Minimum="{Binding MinXAxis}"/>
</syncfusion:SfChart.PrimaryAxis>
<syncfusion:SfChart.SecondaryAxis>
<syncfusion:NumericalAxis Header="Kgf"/>
</syncfusion:SfChart.SecondaryAxis>
<syncfusion:SfChart.Series>
<syncfusion:FastLineBitmapSeries ItemsSource="{Binding FyCircularBuffer}"
XBindingPath="XData" YBindingPath="YData"
StrokeThickness="1" Interior="DarkGreen"
ShowTooltip="False" ShowTrackballInfo="False"
IsSeriesVisible="{Binding FyChecked}"/>
<syncfusion:FastLineBitmapSeries ItemsSource="{Binding MyCircularBuffer}"
XBindingPath="XData" YBindingPath="YData"
StrokeThickness="1" Interior="LimeGreen"
ShowTooltip="False" ShowTrackballInfo="False"
IsSeriesVisible="{Binding MyChecked}"/>
<syncfusion:FastLineBitmapSeries ItemsSource="{Binding FxCircularBuffer}"
XBindingPath="XData" YBindingPath="YData"
StrokeThickness="1" Interior="IndianRed"
ShowTooltip="False" ShowTrackballInfo="False"
IsSeriesVisible="{Binding FxChecked}"/>
<syncfusion:FastLineBitmapSeries ItemsSource="{Binding MxCircularBuffer}"
XBindingPath="XData" YBindingPath="YData"
StrokeThickness="1" Interior="Red"
ShowTooltip="False" ShowTrackballInfo="False"
IsSeriesVisible="{Binding MxChecked}"/>
<syncfusion:FastLineBitmapSeries ItemsSource="{Binding FzCircularBuffer}"
XBindingPath="XData" YBindingPath="YData"
StrokeThickness="1" Interior="BlueViolet"
ShowTooltip="False" ShowTrackballInfo="False"
IsSeriesVisible="{Binding FzChecked}"/>
<syncfusion:FastLineBitmapSeries ItemsSource="{Binding MzCircularBuffer}"
XBindingPath="XData" YBindingPath="YData"
StrokeThickness="1" Interior="Blue"
ShowTooltip="False" ShowTrackballInfo="False"
IsSeriesVisible="{Binding MzChecked}"/>
</syncfusion:SfChart.Series>
</syncfusion:SfChart>
</DockPanel>
</Border>
<!--COP BORDER-->
<Border Grid.Row="2"
Grid.Column="3"
Margin="5"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="5"
Padding="10"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<DockPanel>
<Label Content="C O P"
Grid.Column="0" Grid.ColumnSpan="2"
DockPanel.Dock="Top"
HorizontalAlignment="Center"
FontSize="16"
FontWeight="Bold"
Padding="0,0,0,5"/>
<syncfusion:SfChart x:Name="CopChart"
VerticalContentAlignment="Stretch"
HorizontalAlignment="Center"
AreaBorderThickness="5"
AreaBorderBrush="#523B97">
<syncfusion:SfChart.PrimaryAxis>
<syncfusion:NumericalAxis Minimum="-20"
Maximum="20"
Interval="10"
PlotOffset="5"
BorderThickness="0"
TickLineSize="0"
FontSize="12">
<syncfusion:NumericalAxis.AxisLineStyle>
<Style TargetType="Line">
<Setter Property="StrokeThickness" Value="0"/>
</Style>
</syncfusion:NumericalAxis.AxisLineStyle>
</syncfusion:NumericalAxis>
</syncfusion:SfChart.PrimaryAxis>
<syncfusion:SfChart.SecondaryAxis>
<syncfusion:NumericalAxis Minimum="-30"
Maximum="30"
Interval="10"
PlotOffset="5"
BorderThickness="0"
TickLineSize="0"
FontSize="12">
<syncfusion:NumericalAxis.AxisLineStyle>
<Style TargetType="Line">
<Setter Property="StrokeThickness" Value="0"/>
</Style>
</syncfusion:NumericalAxis.AxisLineStyle>
</syncfusion:NumericalAxis>
</syncfusion:SfChart.SecondaryAxis>
<syncfusion:SfChart.Annotations>
<syncfusion:LineAnnotation X1="00" X2="00" Y1="-30" Y2="30"
Stroke="DimGray" StrokeThickness="2" StrokeDashArray="4"/>
<syncfusion:LineAnnotation X1="-20" X2="20" Y1="00" Y2="0"
Stroke="DimGray" StrokeThickness="2" StrokeDashArray="4"/>
</syncfusion:SfChart.Annotations>
<syncfusion:SfChart.Series>
<syncfusion:FastScatterBitmapSeries ItemsSource="{Binding COP_DOT}"
XBindingPath="XData" YBindingPath="YData"
Interior="Red" ScatterHeight="20" ScatterWidth="20"/>
</syncfusion:SfChart.Series>
</syncfusion:SfChart>
</DockPanel>
</Border>
<!--SERIAL BORDER-->
<Border Grid.Row="1"
Grid.Column="3"
Margin="5"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="5"
Padding="15,0,15,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Content="Conexão"
Grid.Column="0" Grid.ColumnSpan="2"
DockPanel.Dock="Top"
HorizontalAlignment="Center"
FontSize="16"
FontWeight="Bold"
Padding="0,0,0,5"/>
<DockPanel Grid.Row="1" Grid.ColumnSpan="2" LastChildFill="True">
<Label Content="Porta COM: "></Label>
<ComboBox ItemsSource="{Binding Available_COMPorts}"
SelectedValue="{Binding SelectedCOMPort, Mode=TwoWay}"
Margin="0,0,0,15" />
<!--SelectedItem="{Binding SelectedCOMPort, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}"-->
</DockPanel>
<Button Grid.Row="2" Grid.ColumnSpan="2" Content="Connect/Disconnect" HorizontalAlignment="Stretch" Margin="5"
Command="{Binding Path=ConnectButtonCommand, Source={StaticResource LIVviewModel}}"/>
<StackPanel Orientation="Horizontal" Grid.Row="3" Grid.ColumnSpan="2">
<Label Content="Status: "></Label>
<Label Content="{Binding SelectedCOMPort}" Foreground="Red" />
</StackPanel>
</Grid>
</Border>
<Grid Grid.Row="3"
Grid.Column="1" Grid.ColumnSpan="3"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!--SERIAL BORDER-->
<Border Grid.Column="0"
Margin="5"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="5"
Padding="20,0,20,0">
</Border>
<!--RECORD BORDER-->
<Border Grid.Column="1"
Margin="5"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="5"
Padding="20,0,20,0">
<Grid Margin="5,10,5,10" VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1.5*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button Grid.Row="0" Grid.ColumnSpan="2" Margin="0,0,0,10"
Content="Click me" Command="{Binding Path=RecordButtonCommand, Source={StaticResource LIVviewModel}}" />
<Label Content="Record Time (s):"
Grid.Column="0" Grid.Row="1" VerticalAlignment="Center"/>
<TextBlock Text="30" Background="White"
Grid.Column="1" Grid.Row="1"
VerticalAlignment="Center"/>
<Label Content="Name:"
Grid.Column="0" Grid.Row="2" VerticalAlignment="Center"/>
<TextBlock Text="Subject Name"
Grid.Column="1" Grid.Row="2" VerticalAlignment="Center"/>
</Grid>
</Border>
<!--Informações do Gráfico BORDER-->
<Border Grid.Column="2"
Margin="5"
BorderBrush="Black"
BorderThickness="2"
CornerRadius="5"
Padding="10,0,10,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Content="Informações do Gráfico"
Grid.Column="0" Grid.ColumnSpan="6"
HorizontalAlignment="Center"
FontSize="16" FontWeight="Bold" />
<CheckBox Content="Fy"
Grid.Column="0" Grid.Row="1"
FontWeight="ExtraBlack" FontSize="12"
Foreground="DarkGreen"
HorizontalAlignment="Center"
IsChecked="{Binding FyChecked}" />
<CheckBox Content="My"
Grid.Column="1" Grid.Row="1"
FontWeight="ExtraBlack" FontSize="12"
Foreground="LimeGreen"
HorizontalAlignment="Center"
IsChecked="{Binding MyChecked}" />
<CheckBox Content="Fx"
Grid.Column="2" Grid.Row="1"
FontWeight="ExtraBlack" FontSize="12"
Foreground="IndianRed"
HorizontalAlignment="Center"
IsChecked="{Binding FxChecked}" />
<CheckBox Content="Mx"
Grid.Column="3" Grid.Row="1"
FontWeight="ExtraBlack" FontSize="12"
Foreground="Red"
HorizontalAlignment="Center"
IsChecked="{Binding MxChecked}" />
<CheckBox Content="Fz"
Grid.Column="4" Grid.Row="1"
FontWeight="ExtraBlack" FontSize="12"
Foreground="BlueViolet"
HorizontalAlignment="Center"
IsChecked="{Binding FzChecked}" />
<CheckBox Content="Mz"
Grid.Column="5" Grid.Row="1"
FontWeight="ExtraBlack" FontSize="12"
Foreground="Blue"
HorizontalAlignment="Center"
IsChecked="{Binding MzChecked}" />
<DockPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" LastChildFill="True" HorizontalAlignment="Stretch">
<Label Content="Periodo: " VerticalAlignment="Center"/>
<ComboBox Text="30" Background="White" VerticalAlignment="Center"
SelectedIndex="{Binding PeriodSelectedIndex}"> <!--5-->
<ComboBoxItem Content="100 ms" />
<ComboBoxItem Content="500 ms" />
<ComboBoxItem Content="1 s" />
<ComboBoxItem Content="5 s" />
<ComboBoxItem Content="10 s" />
<ComboBoxItem Content="30 s" />
</ComboBox>
</DockPanel>
<DockPanel Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3" LastChildFill="True" HorizontalAlignment="Stretch">
<Label Content="Amplitude: " VerticalAlignment="Center"/>
<ComboBox Text="30" Background="White" VerticalAlignment="Center"
SelectedIndex="{Binding AmplitudeSelectedIndex}"> <!--3-->
<ComboBoxItem Content="10"/>
<ComboBoxItem Content="100"/>
<ComboBoxItem Content="500"/>
<ComboBoxItem Content="1000"/>
<ComboBoxItem Content="5000"/>
<ComboBoxItem Content="10000"/>
</ComboBox>
</DockPanel>
<Label Grid.Row="2" Grid.Column="3" Grid.ColumnSpan="3" Content="{Binding PesoKg}" ContentStringFormat="Peso (kg): {0}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Label Grid.Row="3" Grid.Column="3" Grid.ColumnSpan="3" Content="{Binding PesoNw}" ContentStringFormat="Força (N): {0}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Border>
</Grid>
</Grid>
</UserControl>
Connect Button
public class ConnectButtonCommand : ICommand
{
public LiveGraphViewModel ViewModel { get; set; }
public ConnectButtonCommand(LiveGraphViewModel viewModel)
{
this.ViewModel = viewModel;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.ViewModel.ConnectButton();
}
}
DataContext
public partial class LiveGraphView : UserControl
{
private LiveGraphViewModel _vm;
public LiveGraphView()
{
InitializeComponent();
this._vm = new LiveGraphViewModel();
this.DataContext = this._vm;
}
}
As mentioned by #Clemens I had two view model instances at the same time.
One created in the UserControl's constructor (code behind) and other on the XAML Resources.
So, I removed the last one and everything works fine.
<!--
<UserControl.Resources>
<viewmodels:LiveGraphViewModel x:Key="LIVviewModel"/>
</UserControl.Resources>
-->
<Label Content="Porta COM: "></Label>
<ComboBox ItemsSource="{Binding Available_COMPorts}"
SelectedValue="{Binding SelectedCOMPort, Mode=TwoWay}" />
<Button Content="Connect/Disconnect"
Command="{Binding Path=ConnectButtonCommand}" />

How to Bind Textbox.ReadOnly to property in the parent (owner) view model

It seems a resolved question but after trying several answers posted here I could not resolve my problem. Here it is:
I want to set (by binding) the property IsReadonly of a TextBox to a property of the main view model which contains the type to which the TextBox is already bound to. Also de text box is in a DataTemplate bound to a type.
Trying this produces "Property 'IsReadOnly' is not found" which has sense because the data template is bound to a type that doesn't have it:
<TextBox Name="PromoEntryForm" AutomationProperties.Name="Promo Description" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=FriendlyName, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5"
IsReadOnly="{Binding Path=IsReadOnly}" />
Trying this produces nothing, which also has sense because it is binding to the same TextBox.IsReadOnly, as I understand:
<TextBox Name="DiscountEntryForm" AutomationProperties.Name="PromoDiscount" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5" IsReadOnly="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=IsReadOnly}" />
I know I have to bind to a property in the parent (container view model) so I also tried this, which produces "System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='CataloGo.ViewModel.PromoViewModel', AncestorLevel='1''. BindingExpression:Path=IsReadOnly; DataItem=null; target element is 'TextBox' (Name='PrintableNameEntryForm'); target property is 'IsReadOnly' (type 'Boolean')"
<TextBox Name="PrintableNameEntryForm" AutomationProperties.Name="Printable Name" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=PublicName, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5"
IsReadOnly="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type localVm:PromoViewModel}}, Path=IsReadOnly}" />
But the type is the one in the error message and the DataContext of the view is set to an instance of PromoViewModel so I don't know why it cannot find the source.
This is the full view model:
public class PromoViewModel : SupervisedEntity
{
private ObservableCollection<Promo> _promos;
private Promo _current;
private bool _editMode;
private bool _isNew;
public string Title => "Promociones";
public ObservableCollection<Promo> Promos
{
get => this._promos;
set => this.SetPropertyValue(ref this._promos, value);
}
public Promo Current
{
get => this._current;
set => this.SetPropertyValue(ref this._current, value);
}
public bool IsEditMode
{
get => this._editMode;
set
{
this.SetPropertyValue(ref this._editMode, value);
this.NotifyPropertyChanged(nameof(this.IsReadOnly));
this.NotifyPropertyChanged(nameof(this.HideIfEditing));
this.NotifyPropertyChanged(nameof(this.ShowIfEditing));
}
}
public bool IsNew
{
get => this._isNew;
set => this.SetPropertyValue(ref this._isNew, value);
}
public Promo Backup { get; set; }
public bool IsReadOnly => !this.IsEditMode;
public Visibility HideIfEditing => (this.IsReadOnly ? Visibility.Visible : Visibility.Collapsed);
public Visibility ShowIfEditing => (this.IsReadOnly ? Visibility.Collapsed : Visibility.Visible);
}
And this is the corresponding view:
<Window x:Class="CataloGo.View.PromoWindow"
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:localroot="clr-namespace:CataloGo"
xmlns:local="clr-namespace:CataloGo.View"
xmlns:localVm="clr-namespace:CataloGo.ViewModel"
xmlns:localModel="clr-namespace:CataloGo.Model"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance localVm:PromoViewModel, d:IsDesignTimeCreatable=True}"
Title="{Binding Path=Title}" SizeToContent="WidthAndHeight" MinWidth="640" MinHeight="480">
<Window.Resources>
<CollectionViewSource
Source="{Binding Path=Promos}"
x:Key="PromoListingDataView" />
<DataTemplate x:Key="PromoDetailTemplate" DataType="{x:Type localModel:Promo}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="106" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Style="{StaticResource SmallTitleStyle}"
Margin="5">
Descripción:
</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0"
Style="{StaticResource SmallTitleStyle}" Margin="0,5,0,5">
Descuento (%):
</TextBlock>
<TextBox Name="PromoEntryForm" AutomationProperties.Name="Promo Description" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=FriendlyName, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5"
IsReadOnly="{Binding Path=IsReadOnly}" />
<TextBox Name="DiscountEntryForm" AutomationProperties.Name="PromoDiscount" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5" IsReadOnly="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=IsReadOnly}" >
<TextBox.Text>
<Binding Path="DiscountPct" StringFormat="N2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</DataTemplate>
</Window.Resources>
<Border Padding="20">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="300"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
Style="{StaticResource TitleStyle}"
Margin="5">
Promociones:
</TextBlock>
<Border Grid.Row="2" Grid.ColumnSpan="3" Style="{StaticResource BorderStyle}">
<ListView Name="ListViewPromos" Grid.Row="1" Grid.ColumnSpan="3" ItemsSource="{Binding Source={StaticResource PromoListingDataView}}"
IsSynchronizedWithCurrentItem="True"
SelectionChanged="OnSelectionChanged"
MouseDoubleClick="EnterEditMode"
IsEnabled="{Binding Path=IsReadOnly}">
<ListView.View>
<GridView AllowsColumnReorder="true" ColumnHeaderToolTip="Info de la promoción">
<GridViewColumn DisplayMemberBinding="{Binding Path=FriendlyName}" Header="Nombre de la promo" Width="300"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=DiscountPct, StringFormat=N2}" Header="Descuento (%)" Width="150"/>
</GridView>
</ListView.View>
</ListView>
</Border>
<Grid Grid.Row="4" Grid.ColumnSpan="3">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentControl Name="PromoDetail" Grid.Row="0" Grid.ColumnSpan="2"
Content="{Binding Path=Current}"
ContentTemplate="{StaticResource PromoDetailTemplate}"
Margin="9,0,0,0" />
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="0,0,0,0"/>
</Style>
</StackPanel.Resources>
<Button Name="NewPromoButton" HorizontalAlignment="Right" Content="_Nueva" Style="{StaticResource AcctionButtonStyle}"
Click="NewPromo" Visibility="{Binding Path=HideIfEditing}"/>
<Button Name="EditPromoButton" HorizontalAlignment="Right" Content="_Modificar" Style="{StaticResource AcctionButtonStyle}"
Click="EditPromo" Visibility="{Binding Path=HideIfEditing}"/>
<Button Name="SubmitPromoButton" HorizontalAlignment="Right" Content="_Aceptar" Style="{StaticResource AcctionButtonStyle}"
Click="SubmitPromo" Visibility="{Binding Path=ShowIfEditing}"/>
<Button Name="CancelEditButton" HorizontalAlignment="Right" Content="_Cancelar" Style="{StaticResource AcctionButtonStyle}"
Click="UndoEdit" Visibility="{Binding Path=ShowIfEditing}"/>
</StackPanel>
</Grid>
</Grid>
</Border>
Use Element Binding instead of relative source.
First Provide name for the Window.
<Window x:Class="CataloGo.View.PromoWindow"
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:localroot="clr-namespace:CataloGo"
xmlns:local="clr-namespace:CataloGo.View"
xmlns:localVm="clr-namespace:CataloGo.ViewModel"
xmlns:localModel="clr-namespace:CataloGo.Model"
mc:Ignorable="d"
x:Name="Window1"
d:DataContext="{d:DesignInstance localVm:PromoViewModel, d:IsDesignTimeCreatable=True}"
Title="{Binding Path=Title}" SizeToContent="WidthAndHeight" MinWidth="640" MinHeight="480">
Element Binding Code in xaml
<TextBox Name="DiscountEntryForm" AutomationProperties.Name="PromoDiscount" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5" IsReadOnly="{Binding ElementName=Window1, Path=DataContext.IsReadOnly}" >
<TextBox.Text>
<Binding Path="DiscountPct" StringFormat="N2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

Two ListBoxes with two (almost similar) templates

I have 2 Listboxes with 2 data templates that are almost identical except that one of them contains TextBox instead of ComboBox.
First Template :
<DataTemplate x:Key="OldPanelsTemplate" DataType="{x:Type VM:CustomPanelBoard}">
<Grid Height="60" Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
Margin="0 0 2 0" >
<TextBlock Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue"
Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}, Path=(ItemsControl.AlternationIndex),
Converter={StaticResource IncrementerConverter}}" />
</Border>
<TextBlock Grid.Column="1" Text="{Binding Name}" />
<TextBlock Grid.Column="2" Text="{Binding DistributionSystemName}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="Number of circuits to be copied: "/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfCircuitsToBeCopied}" />
</Grid>
</DataTemplate>
Second Template :
<DataTemplate x:Key="NewPanelsTemplate" DataType="{x:Type VM:CustomPanelBoard}">
<Grid Height="60">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
Margin="0 0 2 0" >
<TextBlock Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue"
Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}, Path=(ItemsControl.AlternationIndex),
Converter={StaticResource IncrementerConverter}}" />
</Border>
<TextBlock Grid.Column="1" Text="{Binding Name}" />
<ComboBox Grid.Column="2" ItemsSource="{Binding ValidDistributionSystemsForPanel}"
SelectedItem="{Binding SelectedValidDistributionSystemsForPanel}" HorizontalAlignment="Stretch"
IsHitTestVisible="{Binding DistributionSystemNotAssigned}" IsEnabled="{Binding DistributionSystemNotAssigned}"
ItemTemplate="{StaticResource DistributionSystemTemplate}" >
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="1" Text="Number of available ways: "/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfAvailableWays}" />
</Grid>
</DataTemplate>
As you can see they are both almost identical except for this part :
<ComboBox Grid.Column="2" ItemsSource="{Binding ValidDistributionSystemsForPanel}"
SelectedItem="{Binding SelectedValidDistributionSystemsForPanel}" HorizontalAlignment="Stretch"
IsHitTestVisible="{Binding DistributionSystemNotAssigned}" IsEnabled="{Binding DistributionSystemNotAssigned}"
ItemTemplate="{StaticResource DistributionSystemTemplate}" >
</ComboBox>
The problem is whenever i change anything in one of them i have to change the same thing in the other also ... Any way that can merge them in some way and make the combobox the only variable that changes according to which listbox is calling the template ?
Here an attempt on how you can achieve that, but I have to admit that it is messy but answers your question blindly! a better approach would be to properly implement a DataTemplateSelector.
The idea is to decouple the changing parts into two separate DataTemplates and put them into the resources, one with the Combobox and one for the TextBlock in your case:
<DataTemplate x:Key="DataTemplateCombobox">
<ComboBox ItemsSource="{Binding ValidDistributionSystemsForPanel}" ...>
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="DataTemplateTextblock" >
<TextBlock Text="{Binding DistributionSystemName}" ... />
</DataTemplate>
Now those controls will be replaced with a ContentPresenter in your main (common) DataTemplate. The ContentPresenter uses a ContentTemplateSelector to select which sub-DataTemplate to used based on the name of the ListBox on which this DataTemplate is applied, this is why the Content is bound directly o the ListBox using ancestry binding:
<local:ValueDataTemplateSelector x:Key="TemplateSelector"
DefaultDataTemplate="{StaticResource DataTemplateTextblock}"
ComboboxDataTemplate="{StaticResource DataTemplateCombobox}"
TextBlockDataTemplate="{StaticResource DataTemplateTextblock}" />
<DataTemplate x:Key="OldPanelsTemplate">
<Grid Height="60" Margin="0" Name="OldPanelsTemplateGrid" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
Margin="0 0 2 0" >
<TextBlock Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue" Text="tex" />
</Border>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" />
<ContentPresenter ContentTemplateSelector="{StaticResource TemplateSelector}" Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Grid.Row="0" Grid.Column="2">
</ContentPresenter>
<TextBlock Grid.Row="1" Grid.Column="1" Text="Number of circuits to be copied: "/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfCircuitsToBeCopied}" />
</Grid>
</DataTemplate>
And here how your DataTemplateSelector should be implemented (basically):
public class ValueDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultDataTemplate { get; set; }
public DataTemplate ComboboxDataTemplate { get; set; }
public DataTemplate TextBlockDataTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var lb = item as ListBox;
if (lb is null)
return DefaultDataTemplate;
if (lb.Name == "ListOne")
return ComboboxDataTemplate;
if (lb.Name == "ListTwo")
return TextBlockDataTemplate;
return DefaultDataTemplate;
}
}
Finally, since your sub-DataTemplates lose their DataContexts due to the Content of the ContentPresenter being bound to the ListBox directly, then just hook their DataContexts again using ElementName Binding or something:
<DataTemplate x:Key="DataTemplateCombobox">
<ComboBox DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}" ItemsSource="{Binding ValidDistributionSystemsForPanel}" >
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="DataTemplateTextblock" >
<TextBlock Text="{Binding DistributionSystemName}" DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}"/>
</DataTemplate>
OldPanelsTemplateGrid is the First Grid in the main DataTemplate that should have the valid ListBoxItem DataContext.
Here is the full Xaml code:
</Window ...
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Title="MainWindow" Height="450" Width="800" >
<Window.Resources>
<DataTemplate x:Key="DataTemplateCombobox">
<ComboBox DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}" ItemsSource="{Binding ValidDistributionSystemsForPanel}" >
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="DataTemplateTextblock" >
<TextBlock Text="{Binding DistributionSystemName}" DataContext="{Binding ElementName=OldPanelsTemplateGrid, Path=DataContext}"/>
</DataTemplate>
<local:ValueDataTemplateSelector x:Key="TemplateSelector"
DefaultDataTemplate="{StaticResource DataTemplateTextblock}"
ComboboxDataTemplate="{StaticResource DataTemplateCombobox}"
TextBlockDataTemplate="{StaticResource DataTemplateTextblock}" />
<DataTemplate x:Key="OldPanelsTemplate">
<Grid Height="60" Margin="0" Name="OldPanelsTemplateGrid" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
Margin="0 0 2 0" >
<TextBlock Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue" Text="tex" />
</Border>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" />
<ContentPresenter ContentTemplateSelector="{StaticResource TemplateSelector}" Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" Grid.Row="0" Grid.Column="2">
</ContentPresenter>
<TextBlock Grid.Row="1" Grid.Column="1" Text="Number of circuits to be copied: "/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfCircuitsToBeCopied}" />
</Grid>
</DataTemplate>
</Window.Resources>
<!--DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorLevel=2,AncestorType=DataTemplate}}"-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox x:Name ="ListOne" ItemsSource="{Binding MyCollection}" ItemTemplate="{StaticResource OldPanelsTemplate}"/>
<ListBox x:Name ="LisTwo" ItemsSource="{Binding MyCollection}" ItemTemplate="{StaticResource OldPanelsTemplate}" Grid.Row="1"/>
</Grid>
I managed to solve it by merging them in one template like this:
<DataTemplate x:Key="NewOldPanelsTemplate" DataType="{x:Type VM:CustomPanelBoard}">
<Grid Height="60">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="35"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border Height="60" Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Center" BorderBrush="Blue" BorderThickness="1" Width="35" Background="Blue"
Margin="0 0 2 0" >
<TextBlock Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Blue"
Text="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}, Path=(ItemsControl.AlternationIndex),
Converter={StaticResource IncrementerConverter}}" />
</Border>
<TextBlock Grid.Column="1" Text="{Binding Name}" />
<!-- To have same template for new and old panels we had the two elements (combobox and textblock) for distribution system and toggle visibility by converters according to their groupbox title -->
<ComboBox Grid.Column="2" ItemsSource="{Binding ValidDistributionSystemsForPanel}"
SelectedItem="{Binding SelectedValidDistributionSystemsForPanel}" HorizontalAlignment="Stretch"
IsHitTestVisible="{Binding DistributionSystemNotAssigned}" IsEnabled="{Binding DistributionSystemNotAssigned}"
ItemTemplate="{StaticResource DistributionSystemTemplate}"
Visibility="{Binding RelativeSource={RelativeSource AncestorType=GroupBox, Mode=FindAncestor}, Path=Header,Converter={StaticResource NewPanelsTemplateVisibilityConverter}}">
</ComboBox>
<TextBlock Grid.Column="2" Text="{Binding DistributionSystemName}"
Visibility="{Binding RelativeSource={RelativeSource AncestorType=GroupBox, Mode=FindAncestor}, Path=Header,Converter={StaticResource OldPanelsTemplateVisibilityConverter}}"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="Number of available ways: "/>
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding NumberOfAvailableWays}" />
</Grid>
</DataTemplate>
and control the visibility of variable parts(ComboBox and TextBlock in my case) with converters like this :
public class OldPanelsTemplateVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((string)value == "Old Panels")
{
return Visibility.Visible;
}
return Visibility.Collapsed;
}
}
public class NewPanelsTemplateVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((string)value == "Old Panels")
{
return Visibility.Collapsed;
}
return Visibility.Visible;
}}
The solution above is based on that each of my listboxes is surrounded by groupbox with certain header (In other cases you may want to use ListBox Names in the converter as visibility toggle)
Thanks to #PavelAnikhouski for the idea of converters.
Also i tried the solution of DataTemplateSelector proposed by #SamTheDev and it worked also (Thanks #SamTheDev), but i preferred the converter solution because i didn't feel comfortable with the idea of losing datacontext through using content presenter (The bottom line here is both solutions work and no one is more elegant it is just personal preference)

WPF MVVM DataGrid DoubleClick not working when row is already selected

So I have a problem identical to this question.
However, the solution to this appears to be Code-Behind (and is 4 years old) but I am using MVVM. Time may have brought about a better solution to this.
Does anyone know of a way to have the double click work on a DataGridRow after it has been selected?
Here is my XAML:
<DataGrid x:Name="ContactsResult"
AlternatingRowBackground="Transparent"
AutoGenerateColumns="False"
BorderBrush="{StaticResource DarkBlueBrush}"
BorderThickness="1"
CanUserAddRows="False"
FontFamily="{StaticResource DefaultFont}"
GridLinesVisibility="None"
HeadersVisibility="Column"
IsReadOnly="True"
ItemsSource="{Binding Results, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
>
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding DetailsCommand}"
CommandParameter="{Binding ElementName=ContactsResult, Path=SelectedItem.ContactId}"
/>
</DataGrid.InputBindings>
<DataGrid.Columns>
<DataGridTextColumn x:Name="contactLastNameColumn"
Binding="{Binding Name_LastSufFirstMidSal}"
ElementStyle="{StaticResource dgCellPadding5}" Header="Contact Name"
Width="Auto"
/>
<DataGridTextColumn x:Name="companyNameColumn"
ElementStyle="{StaticResource dgCellPadding5}" Header="Company Name"
Width="Auto"
/>
<DataGridTextColumn x:Name="CityColumn"
Binding="{Binding MailingAddress.City}"
ElementStyle="{StaticResource dgCellPadding5}" Header="Mailing City"
Width="Auto"
/>
<DataGridTextColumn x:Name="StateColumn"
Binding="{Binding MailingAddress.StateOrProvince}" ElementStyle="{StaticResource dgCellPadding5}" Header="Mailing State"
Width="Auto"
/>
</DataGrid.Columns>
</DataGrid>
EDIT
ElementSytle
<Style x:Key="dgCellPadding5" TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5" />
</Style>
The following sample, targeting .NET 4.7.1, fires double-click commands (regardless of selection status). I've included the style you referenced, from above. Is your code any different than this?
<Window x:Class="WpfApp1.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:wpfApp1="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<wpfApp1:ViewModel />
</Window.DataContext>
<Window.Resources>
<Style x:Key="dgCellPadding5" TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5" />
</Style>
</Window.Resources>
<DataGrid x:Name="ContactsResult"
AlternatingRowBackground="Transparent"
AutoGenerateColumns="False"
BorderThickness="1"
CanUserAddRows="False"
GridLinesVisibility="None"
HeadersVisibility="Column"
IsReadOnly="True"
ItemsSource="{Binding Contacts}"
SelectedItem="{Binding SelectedContact, Mode=TwoWay}">
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding Command}"
CommandParameter="{Binding ElementName=ContactsResult, Path=SelectedItem}" />
</DataGrid.InputBindings>
<DataGrid.Columns>
<DataGridTextColumn ElementStyle="{StaticResource dgCellPadding5}"
Binding="{Binding ContactName}"
Header="Contact Name"
Width="Auto" />
<DataGridTextColumn ElementStyle="{StaticResource dgCellPadding5}" Header="Company Name"
Binding="{Binding CompanyName}"
Width="Auto" />
<DataGridTextColumn Binding="{Binding City}" Header="Mailing City"
Width="Auto" ElementStyle="{StaticResource dgCellPadding5}" />
<DataGridTextColumn Binding="{Binding State}" Header="Mailing State"
Width="Auto" ElementStyle="{StaticResource dgCellPadding5}" />
</DataGrid.Columns>
</DataGrid>
using System;
using System.Diagnostics;
using System.Windows.Input;
using Prism.Commands;
using Prism.Mvvm;
namespace WpfApp1
{
public class ViewModel : BindableBase
{
public ContactInfo[] Contacts
{
get;
} =
{
new ContactInfo
{
ContactName = "First",
CompanyName = "Acme",
City = "Somewhere",
State = "CA"
},
new ContactInfo
{
ContactName = "Second",
CompanyName = "Acme",
City = "Somewhere",
State = "CA"
},
new ContactInfo
{
ContactName = "Third",
CompanyName = "Acme",
City = "Somewhere",
State = "CA"
},
new ContactInfo
{
ContactName = "Fourth",
CompanyName = "Acme",
City = "Somewhere",
State = "CA"
}
};
private ContactInfo _selectedContact;
public ContactInfo SelectedContact
{
get => _selectedContact;
set => SetProperty(ref _selectedContact, value);
}
public ICommand Command
{
get;
} = new DelegateCommand<ContactInfo>(ci => Debug.WriteLine($"{ci.ContactName} {DateTime.Now}"));
public ICommand OtherCommand
{
get;
} = new DelegateCommand<string>(s => Debug.WriteLine($"{s} {DateTime.Now}"));
}
public class ContactInfo
{
public string ContactName
{
get;
set;
}
public string CompanyName
{
get; set;
}
public string City
{
get;
set;
}
public string State
{
get;
set;
}
}
}
I use Prism frame,this is my code :
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<controls:CustomDataGrid Grid.Row="2"
SelectedItem="{Binding ModelItem}"
ItemsSource="{Binding ModelList}"
IsReadOnly="True"
Style="{StaticResource DataTriggerDataGridStyle}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding BtnNoticeViewCommand, IsAsync=True}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
So in order to reach my goal, I decided to go a different route.
First I started with a Grid and used the attribute Grid.IsSharedSizeScope="True". In this grid I created a grid and an ItemsControl (with a grid in the datatemplate). This gives me the appearance of a datagrid.
Second, in the itemscontrol I added InputBindings to each item to achieve the desired behaviour. Here is what I came up with:
<Border Grid.Row="2"
BorderBrush="{StaticResource BackgroundTitleBarBrush}"
BorderThickness="1"
Visibility="{Binding ResultGridVisible, Converter={local:BooleanToVisibilityValueConverter}, ConverterParameter=True}"
>
<Grid Background="White"
Grid.Row="2"
Grid.IsSharedSizeScope="True"
>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Border BorderBrush="{StaticResource SeperatorDarkBlueBrush}"
BorderThickness="0 0 0 1"
Margin="5 0"
Padding="3 5"
Grid.Row="0">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A" Width="150"/>
<ColumnDefinition SharedSizeGroup="B" Width="200"/>
<ColumnDefinition SharedSizeGroup="C" Width="100"/>
<ColumnDefinition SharedSizeGroup="D" Width="75"/>
<ColumnDefinition SharedSizeGroup="E" Width="100"/>
<ColumnDefinition SharedSizeGroup="F" Width="50"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock FontWeight="Bold"
Foreground="{StaticResource BackgroundTitleBarBrush}"
Grid.Column="0"
Text="Contact Name"
/>
<TextBlock FontWeight="Bold"
Foreground="{StaticResource BackgroundTitleBarBrush}"
Grid.Column="1"
Text="Company Name"
/>
<TextBlock FontWeight="Bold"
Foreground="{StaticResource BackgroundTitleBarBrush}"
Grid.Column="2"
Grid.ColumnSpan="2"
Text="Mailing"
/>
<TextBlock FontWeight="Bold"
Foreground="{StaticResource BackgroundTitleBarBrush}"
Grid.Column="2"
Grid.Row="1"
Text="City"
/>
<TextBlock FontWeight="Bold"
Foreground="{StaticResource BackgroundTitleBarBrush}"
Grid.Column="3"
Grid.Row="1"
Text="State"
/>
<TextBlock FontWeight="Bold"
Foreground="{StaticResource BackgroundTitleBarBrush}"
Grid.Column="4"
Grid.ColumnSpan="2"
Text="Other"
/>
<TextBlock FontWeight="Bold"
Foreground="{StaticResource BackgroundTitleBarBrush}"
Grid.Column="4"
Grid.Row="1"
Text="City"
/>
<TextBlock FontWeight="Bold"
Foreground="{StaticResource BackgroundTitleBarBrush}"
Grid.Column="5"
Grid.Row="1"
Text="State"
/>
</Grid>
</Border>
<ScrollViewer Grid.Row="1"
HorizontalScrollBarVisibility="Disabled"
MaxHeight="350"
VerticalScrollBarVisibility="Auto"
>
<ItemsControl ItemsSource="{Binding Results}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border x:Name="Container"
BorderBrush="{StaticResource SeperatorLightBlueBrush}"
BorderThickness="0 0 0 1"
Margin="5 0"
Padding="3 5"
>
<Border.InputBindings>
<MouseBinding MouseAction="LeftClick"
Command="{Binding Path=DataContext.DetailsCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:BasePage}}}"
CommandParameter="{Binding ContactId}"
/>
<MouseBinding MouseAction="RightClick"
Command="{Binding Path=DataContext.NewWindowDetailsCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:BasePage}}}"
CommandParameter="{Binding ContactId}"
/>
</Border.InputBindings>
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A"/>
<ColumnDefinition SharedSizeGroup="B"/>
<ColumnDefinition SharedSizeGroup="C"/>
<ColumnDefinition SharedSizeGroup="D"/>
<ColumnDefinition SharedSizeGroup="E"/>
<ColumnDefinition SharedSizeGroup="F"/>
</Grid.ColumnDefinitions>
<TextBlock FontFamily="{StaticResource DefaultFont}"
Grid.Column="0"
Text="{Binding Name_LastSufFirstMidSal}"
VerticalAlignment="Center"
/>
<TextBlock FontFamily="{StaticResource DefaultFont}"
Grid.Column="1"
Text="{Binding Company}"
VerticalAlignment="Center"
/>
<TextBlock FontFamily="{StaticResource DefaultFont}"
Grid.Column="2"
Text="{Binding MailingAddress.City}"
VerticalAlignment="Center"
/>
<TextBlock FontFamily="{StaticResource DefaultFont}"
Grid.Column="3"
Text="{Binding MailingAddress.StateOrProvince}"
VerticalAlignment="Center"
/>
<TextBlock FontFamily="{StaticResource DefaultFont}"
Grid.Column="4"
Text="{Binding OtherAddress.City}"
VerticalAlignment="Center"
/>
<TextBlock FontFamily="{StaticResource DefaultFont}"
Grid.Column="5"
Text="{Binding OtherAddress.StateOrProvince}"
VerticalAlignment="Center"
/>
</Grid>
</Border>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Container" Property="Background" Value="{StaticResource BackgroundLightBlueBrush}"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Border Grid.Row="1" Panel.ZIndex="1" Visibility="{Binding NoRecordsFound, Converter={local:BooleanToCollapsedVisibilityValueConverter}, ConverterParameter=True, Mode=TwoWay}">
<TextBlock Text="No Records Found"
Margin="5 0"
Padding="3 5"
/>
</Border>
</Grid>
</Border>

Update binded child wpf when parent change wpf

Im using several controls in my main window.
one grid at top that has a list of licenses and i want to display the information of the license each time i change the selection of the grid
MainWindow:
<dx:DXWindow
x:Class="LicenceManagerWPF.Forms.frmCustomerLicense"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
dx:ThemeManager.ThemeName="Office2016"
xmlns:ctr="clr-namespace:LicenceManagerWPF.Controls"
Title="CustomerLicence" Height="800" Width="1000"
WindowStartupLocation="CenterScreen" Loaded="DXWindow_Loaded">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition MinHeight="200" Height="200*"/>
<RowDefinition Height="200*"/>
<RowDefinition MinHeight="25" Height="25"/>
</Grid.RowDefinitions>
<StackPanel x:Name="Stack_Top" Orientation="Horizontal" Grid.Row="0" >
<dx:SimpleButton x:Name="btnRefresh" Style="{StaticResource ResourceKey=BtnSmall}" ToolTip="Refresh Licenses" Glyph="{dx:DXImage Image=Refresh_32x32.png}" Content="Resfresh" />
<dx:SimpleButton x:Name="btndNew" Style="{StaticResource ResourceKey=BtnSmall}" ToolTip="New License" Glyph="{dx:DXImage Image=New_32x32.png}" Content="New Customer" />
<dx:SimpleButton x:Name="btnDelete" Style="{StaticResource ResourceKey=BtnSmall}" ToolTip="Delete Licence" Content="Delete" Glyph="{dx:DXImage Image=Cancel_32x32.png}"/>
<dx:SimpleButton x:Name="btnEdit" Style="{StaticResource ResourceKey=BtnSmall}" ToolTip="Edit Customer" Glyph="{dx:DXImage Image=EditContact_32x32.png}" />
<TextBlock Text="Customer: " FontSize="20" Margin="5"/>
<TextBlock Text="{Binding Customer.Name}" Margin="5" FontSize="20"/>
</StackPanel>
<ctr:Licences x:Name="grdLicenses" Grid.Row="1">
</ctr:Licences>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ctr:LicenseDetail x:Name="ct_LicenseDetail" Grid.Column="0"/>
<ctr:LicenceLog x:Name="ct_LicenseLog" Grid.Column="1"/>
</Grid>
</Grid>
The grid is ctr_Licenses and the other control is LicenseDetail which should show the information of the license selected, this is the licenseDetail control:
<UserControl
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:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core" x:Class="LicenceManagerWPF.Controls.LicenseDetail"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="600" x:Name="ctrl_LicenseDetail" >
<UserControl.Resources>
<Style x:Key="SmallMargin" TargetType="FrameworkElement">
<Setter Property="Margin" Value="5"/>
</Style>
<Style x:Key="TabMargin" TargetType="FrameworkElement" >
<Setter Property="Margin" Value="2.5"/>
</Style>
<Style x:Key="SmalllMarginTextBlock" BasedOn="{StaticResource SmallMargin}" TargetType="{x:Type TextBlock}">
</Style>
<Style x:Key="SmallMarginDropDown" BasedOn="{StaticResource SmallMargin }" TargetType="dx:DropDownButton" >
</Style>
<Style x:Key="SmallMarginText" BasedOn="{StaticResource SmallMargin}" TargetType="TextBox">
</Style>
<Style x:Key="ckkStyle" TargetType="dxe:CheckEdit" BasedOn="{StaticResource TabMargin}">
<Setter Property="IsReadOnly" Value="True"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30"/>
<RowDefinition Height="100*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="License Detail" FontSize="16" Grid.ColumnSpan="2" VerticalAlignment="Center" Margin="10" Grid.Row="0" />
<TextBlock Text="Serial Number:" Grid.Column="0" Grid.Row="1" Style="{StaticResource ResourceKey=SmalllMarginTextBlock}"/>
<TextBlock Text="{Binding Path=DataContext.SelectedLicense.SerialNumber, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=Window}}" x:Name="txtSerialNum" Grid.Column="1" Grid.Row="1" Style="{StaticResource ResourceKey=SmalllMarginTextBlock}"/>
<TextBlock Text="Product:" Grid.Column="0" Grid.Row="2" Style="{StaticResource ResourceKey=SmalllMarginTextBlock}" />
<StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="1">
<dx:DropDownButton x:Name="dropDownButton" Width="180" Content="Product" Style="{StaticResource SmallMarginDropDown}">
</dx:DropDownButton>
<TextBlock Text="Status:" Style="{StaticResource SmalllMarginTextBlock}" />
<TextBlock Text="" Width="150" Style="{StaticResource SmalllMarginTextBlock}" />
</StackPanel>
<TextBlock Text="Description:" Style="{StaticResource SmalllMarginTextBlock}" Grid.Row="3" Grid.Column="0" />
<TextBox Text="" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="3" Style="{StaticResource SmallMargin}" Width="350" />
<dx:DXTabControl Grid.Row="4" Grid.ColumnSpan="2">
<dx:DXTabItem x:Name="tabAttributes" Header="License Attributes" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Activation Mode:" Style="{StaticResource TabMargin}" />
<dx:DropDownButton Grid.Row="0" Grid.Column="1" Width="100" HorizontalAlignment="Left" Style="{StaticResource TabMargin}" ButtonKind="Repeat" />
<TextBlock Text="Grace Period:" Style="{StaticResource TabMargin}" Grid.Row="1"/>
<StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal">
<dxe:SpinEdit Width="80" Style="{StaticResource TabMargin}" MinValue="0" MaxValue="100" IsReadOnly="True" Value="10" Mask="n0"/>
<TextBlock Text="Days" Style="{StaticResource TabMargin}" />
</StackPanel>
<dxe:CheckEdit Grid.Row="2" Content="Expiration Date" IsChecked="False" />
<dxe:DateNavigator Grid.Row="2" Grid.Column="1" Width="185" HorizontalAlignment="Left" Style="{StaticResource TabMargin}" />
<TextBlock Text="Max Computers:" Grid.Row="3" Style="{StaticResource TabMargin}"/>
<dxe:SpinEdit Grid.Row="4" Grid.Column="1" HorizontalAlignment="Left" Width="80" Margin="2.5,2.5,2.5,1" MinValue="0" MaxValue="100" IsReadOnly="True" Value="10" Mask="n0"/>
</Grid>
</dx:DXTabItem>
<dx:DXTabItem Header="Product Features" x:Name="tabProductFeatures" Visibility="Visible">
<StackPanel Orientation="Vertical" >
<dxe:CheckEdit Content="Standard" Name="chkStandard" IsReadOnly="True" Style="{StaticResource ckkStyle}" />
<dxe:CheckEdit Content="Admin Mode" Name="chkAdminMode" IsReadOnly="True" Style="{StaticResource ckkStyle}" />
<dxe:CheckEdit Content="Batch Mode" Name="chkBatchMode" IsReadOnly="True" Style="{StaticResource ckkStyle}" />
<dxe:CheckEdit Content="Custom Data Series" Name="chkCustomDataSeries" IsReadOnly="True" Style="{StaticResource ckkStyle}" />
<dxe:CheckEdit Content="Local DataBase" Name="LocalDataBase" IsReadOnly="True" IsChecked="True" Style="{StaticResource ckkStyle}"/>
<dxe:CheckEdit Content="Cloud Data Base" Name="CloudDataBase" Style="{StaticResource ckkStyle}" />
</StackPanel>
</dx:DXTabItem>
<dx:DXTabItem Name="tabComputers" Header="Computers">
<dxg:GridControl Name="grdComputers">
</dxg:GridControl>
</dx:DXTabItem>
<dx:DXTabItem Name="tabMain_Schedule" Header="Maint. Schedule">
<dxg:GridControl>
</dxg:GridControl>
</dx:DXTabItem>
<dx:DXTabItem x:Name="tabFeaturePB" Header="Product Features">
<StackPanel Orientation="Vertical">
<dxe:CheckEdit Content="pb" Name="chkpb" IsReadOnly="True" Style="{StaticResource ckkStyle}" />
<dxe:CheckEdit Content="pb2" Name="chkpb2" IsReadOnly="True" Style="{StaticResource ckkStyle}" />
</StackPanel>
</dx:DXTabItem>
</dx:DXTabControl>
</Grid>
The DataContext class of the mainwindows is this
public class CustomerLicenses
{
private Customer _Customer;
private LicenseList _Licenses;
private License _SelectedLicense;
//private Guid _SelectedSerial;
//public delegate void SelectLicenseEventArg(String SerialNum);
//public event SelectLicenseEventArg SelectLicense;
public CustomerLicenses(Customer Customer)
{
_Customer = Customer;
}
public CustomerLicenses(Customer customer, LicenseList licenses)
{
_Customer = customer;
_Licenses = licenses;
}
public CustomerLicenses()
{
_Customer = new Customer();
_Licenses = new LicenseList();
}
[DataMember]
public Customer Customer
{
get { return _Customer; }
set
{
if (_Customer != value)
{
this._Customer = value;
this.NotifyPropertyChanged("Customer");
}
}
}
[DataMember]
public LicenseList Licenses
{
get { return _Licenses;}
set
{
if (_Licenses != value)
{
this._Licenses = value;
this.NotifyPropertyChanged("Licenses");
}
}
}
private DataTable GetLicensesTable()
{
var dt = new DataTable();
dt.Columns.AddRange(new DataColumn []{
new DataColumn("SerialNumber",typeof(string)),
new DataColumn("Product",typeof(string)),
new DataColumn("Status",typeof(string)),
new DataColumn("ActivationMode",typeof(string)),
new DataColumn("MaxComputers", typeof(int)),
new DataColumn("NumActive",typeof(int)),
new DataColumn("Description",typeof(string)),
new DataColumn("License",typeof(object))
});
_Licenses.ForEach((x) => {
var rw = dt.NewRow();
rw["SerialNumber"] = x.SerialNumber;
rw["Product"] = x.Product.Name;
rw["Status"] = x.Status;
rw["ActivationMode"] = Enum.GetName(typeof(ActivationModeEnum), x.ActivationMode); //x.ActivationMode;
rw["MaxComputers"] = x.MaxComputers;
rw["NumActive"] = Activated(x.Product.ProductId);
rw["Description"] = x.Description;
rw["License"] = x;
dt.Rows.Add(rw);
});
return dt;
}
public DataTable LicensesTable{
get { return GetLicensesTable(); }
}
[DataMember]
public License SelectedLicense {
get { return _SelectedLicense;}
set {
this.NotifyPropertyChanged("SelectedLicense");
_SelectedLicense = value;
}
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propName));
}
}
In this part im populating the textblock with the serial, and is ok it shows when i start the app `cause takes the first license but if i select another license the text does not change.
TextBlock Text="{Binding Path=DataContext.SelectedLicense.SerialNumber, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=Window}}" x:Name="txtSerialNum" Grid.Column="1" Grid.Row="1" Style="{StaticResource ResourceKey=SmalllMarginTextBlock}"
This is how i assaing the value when the grid chanege selection
private void GridRowSelected (object sender, SelectedItemChangedEventArgs e)
{
try
{
var Record = (DataRowView)grdLicenses.grdLicences.SelectedItem;
var SelectedLicense = (License)Record["License"];
_CustomerLicense.SelectedLicense = SelectedLicense;
//Customer_Detail.DataContext = SelectedCustomer;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
How can i update the child when the item is selected in the grid.
Regards
sTrenat, as you said i checked the class and i was wrong implementing the propertyfotify
i changed to this
[DataContract]
public class CustomerLicenses: System.ComponentModel.INotifyPropertyChanged
{
public void NotifyPropertyChanged(string propName)
{
System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
if (this.PropertyChanged != null)
this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propName));
}
}

Resources