Update DataTemplate on PropertyChanged does not work - c#

I have a simple object Action that has a property Code. Depending on its Code, I want to select different DataTemplates a it is also possible for the user to change the Code through a ComboBox.
public class Action : INotifyPropertyChanged
{
public Action()
{
Parameters = new List<Parameter>();
}
public int ActionID { get; set; }
public int StepID { get; set; }
public int Code { get; set; }
[NotMapped]
public List<Parameter> Parameters { get; set; }
}
So I was looking at this answer: https://stackoverflow.com/a/18000310/2877820
I tried the implement the solution like this:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var action = (ASI.RecipeManagement.Data.Action) item;
if (action == null) return null;
PropertyChangedEventHandler lambda = null;
lambda = (o, args) =>
{
if (args.PropertyName == "Code")
{
action.PropertyChanged -= lambda;
var cp = (ContentPresenter)container;
cp.ContentTemplateSelector = null;
cp.ContentTemplateSelector = this;
}
};
action.PropertyChanged += lambda;
if (action.Code == 0)
return NoParamTemplate;
if (action.Code == 1)
return OneParamTemplate;
if (action.Code == 2)
{
if (action.Parameters[0].Type == ParameterInputTypes.List)
{
return ComboBoxParamTemplate;
}
return TwoParamTemplate;
}
return null;
}
Sadly it does not seem to work for me. Can anybody help me out? What am I doing wrong right here?

A DataTemplateSelector does't respond to property change notifications. As a workaround, you could use a ContentControl with DataTriggers in the ItemTemplate, .e.g.:
<ComboBox ...>
<ComboBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource NoParamTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Code}" Value="1">
<Setter Property="ContentTemplate" Value="{StaticResource OneParamTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Code}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource TwoParamTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Related

DataGrid using MVVM Context Menu items depend on whether a Row is Selected or not

I'm updating a small app to MVVM. It uses a DataGrid and the context menu displayed depend upon whether a row is selected or not. My problem is when I right click on an unselected row the row is first selected so the Selected Menu is displayed. The left mouse button is coded to toggle the selection. If I toggle the selection off then click the right mouse button the No Selection Menu is displayed. How can I get the right mouse click to show the no selection menu on an unselected row without having to explicitly deselect the row?
I have created a small example to illustrate the problem.
I'm using Fody to handle property changes; Microsoft MVVM Toolkit.
XAML:
<Window.Resources>
<ContextMenu x:Key="PopupNoSelection">
<MenuItem Name="NewItem" Header="No Item Selection" Command="{Binding NoSelectionClick}" />
</ContextMenu>
<ContextMenu x:Key="PopupSelection">
<MenuItem Name="AmmendItem" Header="Item Selection" Command="{Binding SelectionClick}" />
</ContextMenu>
</Window.Resources>
<Grid>
<DataGrid x:Name="dgToggle" AutoGenerateColumns="False"
CanUserSortColumns="False" CanUserAddRows="False" SelectionMode="Single"
SelectedItem="{Binding ToggleSelected, Mode=TwoWay}" SelectionUnit="FullRow"
ItemsSource="{Binding ToggleRows}">
<DataGrid.Style>
<Style TargetType="{x:Type DataGrid}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItems.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="ContextMenu" Value="{StaticResource ResourceKey=PopupNoSelection}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=SelectedItems.Count, RelativeSource={RelativeSource Self}}" Value="1">
<Setter Property="ContextMenu" Value="{StaticResource ResourceKey=PopupSelection}" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
<DataGrid.Columns>
<DataGridTextColumn Header="Rubbish" Binding="{Binding Rubbish, Mode=OneWay}" />
</DataGrid.Columns>
<!-- Toggle Selected item & set Selected Item Colour -->
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="DoSelectedRow" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="LimeGreen" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
XAML.cs
public partial class DataGridToggleView : Window
{
public DataGridToggleView()
{
InitializeComponent();
}
public void DoSelectedRow(object sender, MouseButtonEventArgs e)
{
DataGridCell cell = sender as DataGridCell;
if (cell != null && !cell.IsEditing)
{
DataGridRow row = FindVisualParent<DataGridRow>(cell);
if (row != null)
{
row.IsSelected = !row.IsSelected;
e.Handled = true;
}
}
}
public static Parent FindVisualParent<Parent>(DependencyObject child) where Parent : DependencyObject
{
DependencyObject parentObject = child;
while (!((parentObject is System.Windows.Media.Visual)
|| (parentObject is System.Windows.Media.Media3D.Visual3D)))
{
if (parentObject is Parent || parentObject == null)
{
return parentObject as Parent;
}
else
{
parentObject = (parentObject as FrameworkContentElement).Parent;
}
}
parentObject = VisualTreeHelper.GetParent(parentObject);
if (parentObject is Parent || parentObject == null)
{
return parentObject as Parent;
}
else
{
return FindVisualParent<Parent>(parentObject);
}
}
}
Model:
public class DataGridToggleModel
{
public string Rubbish { get; set; }
}
ViewModel
public class DataGridToggleViewModel : ObservableObject
{
public ObservableCollection<DataGridToggleModel> ToggleRows { get; set; }
public List<DataGridToggleModel> ToggleList { get; set; }
private DataGridToggleModel _toggleSelected;
public DataGridToggleModel ToggleSelected
{
get { return _toggleSelected; }
set
{
_toggleSelected = value;
}
}
public ICommand MouseLeftClick { get; set; }
public ICommand NoSelectionClick { get; set; }
public ICommand SelectionClick { get; set; }
public DataGridToggleViewModel()
{
MouseLeftClick = new RelayCommand<object>(MouseLeftCommand);
NoSelectionClick = new RelayCommand(NoSelectionCommand);
SelectionClick = new RelayCommand(SelectionCommand);
}
private void MouseLeftCommand( object obj)
{
////MessageBox.Show($"Row Clicked {ToggleSelected.Rubbish}");
//DataGridRow dataGridRow = ToggleSelected as DataGridRow;
//ToggleSelected.IsSelected = !ToggleSelected.IsSelected;
}
private void NoSelectionCommand()
{
MessageBox.Show("No Selection Menu");
}
private void SelectionCommand()
{
MessageBox.Show("Selected Menu");
}
public void PopulateDataGrid()
{
ToggleList = new List<DataGridToggleModel>();
ToggleList.Add(new DataGridToggleModel { Rubbish = "Waste paper" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Empty tins" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Junk mail" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Empty boxes" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Newpapers & Magazines" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Old clothes" });
ToggleList.Add(new DataGridToggleModel { Rubbish = "Packaging" });
ToggleRows = new ObservableCollection<DataGridToggleModel>(ToggleList);
}
}
Any help would be appreciated - thanks.

OnPropertyChanged event after loading properties during runtime

I can't imagine this is an uncommon problem, but been working at it for few days and just can't find answer. My program creates objects/properties on startup. I have a bound datagrid with datagridcheckboxcolumn that allows user to further change the object properties. The problem is the propertychanged event is hitting during the initial data load while I only want the change event to occur afterward when user uses checkboxes. Any help/direction would be appreciated. Here is my code:
public class AvailableFolder
{
public string FolderPath { get; set; }
private bool folderIncluded { get; set; }
public bool FolderIncluded
{
get { return folderIncluded; }
set
{
folderIncluded = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string properName = null)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(properName));
}
System.Windows.MessageBox.Show("property changed");
}
}
At startup, it loops through and updates object properties:
for (int i = 0; i < allFoldersList.Count; i++)
{
string xFolderName = allFoldersList[i].FolderName;
string xFolderPath = allFoldersList[i].FolderPath;
bool xFolderIncluded = allFoldersList[i].FolderIncluded;
if (xFolderIncluded == false)
{
for (int r = 0; r < MainWindow.openedProject.ReleaseFolders.Count; r++)
{
string releaseFolder = MainWindow.openedProject.ReleaseFolders[r].FullReleaseFolderName;
if (xFolderName == releaseFolder)
{
allFoldersList[i].FolderIncluded = true;
}
}
}
}
AllFoldersDataGrid.ItemsSource = allFoldersList;
And my datagrid xaml:
<DataGrid x:Name="AllFoldersDataGrid" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Included" Binding="{Binding FolderIncluded,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
</DataGridCheckBoxColumn>
<DataGridTextColumn Header="Folder" Binding="{Binding FolderName}"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="WhiteSmoke" />
<Style.Triggers >
<DataTrigger Binding="{Binding FolderIncluded}" Value="true">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>

bind Menu using MVVM throw exception

In my View I have a Menu Control which is binded to my ViewModel's Property becauase I want to populate it dynamically.I created a seperated class for my Menu .
Here is my My menu class:
public class MenuItemViewModel : ViewModelBase
{
internal MenuItemViewModel()
{
}
private string _menuText;
public string MenuText
{
get { return _menuText; }
set
{
if (_menuText == value)
return;
_menuText = value;
RaisePropertyChanged("MenuText");
}
}
private ObservableCollection<MenuItemViewModel> _children;
public ObservableCollection<MenuItemViewModel> Children
{
get { return _children; }
set
{
_children = value;
RaisePropertyChanged("Children");
}
}
}
and In my MainViewModel I created an Collection property of my MenuItemViewModel
here is my MainViewModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
LoadMainMenu();
}
#region Menu
private ObservableCollection<MenuItemViewModel> _topMenuItems;
public ObservableCollection<MenuItemViewModel> TopMenuItems
{
get { return _topMenuItems; }
set
{
if (_topMenuItems == value)
return;
_topMenuItems = value;
RaisePropertyChanged("TopMenuItems");
}
}
public void LoadMainMenu()
{
IList<MenuItemViewModel> fileMenuItems = PopulateFileMenuEntries();
_topMenuItems.Add(new MenuItemViewModel() { MenuText = "_File", Children = new ObservableCollection<MenuItemViewModel>(fileMenuItems) });
}
private IList<MenuItemViewModel> PopulateFileMenuEntries()
{
List<MenuItemViewModel> fileMenuItems = new List<MenuItemViewModel>();
fileMenuItems.Add(new MenuItemViewModel() { MenuText = "Open _Recent" });
return fileMenuItems;
}
}
here is my XAML:
<Window.Resources>
<WpfApplication3_ViewModel:MainViewModel x:Key="MainViewModelDataSource"
d:IsDataSource="True" />
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModelDataSource}">
<Menu
ItemsSource="{Binding TopMenuItems}"
Margin="12,0,50,237">
<Menu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding MenuText}" />
<Setter Property="ItemsSource"
Value="{Binding Children}" />
<Style.Triggers>
<DataTrigger Binding="{Binding }"
Value="{x:Null}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Separator Style="{StaticResource {x:Static MenuItem.SeparatorStyleKey}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.Resources>
</Menu>
</Grid>
When the application run it throw an exception "Exception has been thrown by the target of an invocation."
What is wrong with my code
I have had similar problem and I tracked it down to an uninitialised variable. Your _topMenuItems in your Constructor should be
new ObservableCollection<MenuItemViewModel>()
or
ObservableCollection<MenuItemViewModel> _topMenuItems = new ObservableCollection<MenuItemViewModel>();

WPF Binding to collection with constraint

I have an observable collection of objects.
I wish to bind a gridview to this observable collection. But there is a constraint that only objects whose property x has value a, must be binded
How to do that?
I got it working using CollectionView and filter. For others benefit the code is as follows
Solution :
public class CustomerViewModel
{
public ObservableCollection<Customer> Customers
{
get;
set;
}
private ICollectionView _filteredCustomerView;
public ICollectionView FilteredCustomers
{
get { return _filteredCustomerView; }
}
public CustomerViewModel()
{
this.Customers= new ObservableCollection<Customer>();
Customers= GetCustomer();
_filteredCustomerView= CollectionViewSource.GetDefaultView(Customers);
_filteredCustomerView.Filter = MyCustomFilter;
}
private bool MyCustomFilter(object item)
{
Customer cust = item as Customer;
return (cust.Location == "someValue");
}
}
You should use filtering
I prefer using LINQ.
var result = YourCollection.Where(p => p.x.HasValue).ToObservableCollection();
But you should write your own extension to convert to ObservableCollection.
public static ObservableCollection<T> ToObservableCollection<T>
(this IEnumerable<T> source)
{
if (source == null)
throw new ArgumentNullException("source");
return new ObservableCollection<T>(source);
}
Good luck!
I think you could achieve this in XAML by putting a DataTrigger on the style of your GridView. Something like this:
<DataGrid>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<DataTrigger Binding="{Binding IsFiltered}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding IsFiltered}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style>
</DataGrid.Resources>
</DataGrid>

WPF Datagrid not respect change of item property value

my problem is here:
I have some class
public class Component
{
...
private ServiceController service;
...
public int ServiceStatus
{
get
{
switch(service.Status)
{
case ServiceControllerStatus.Stopped:
return 0;
case ServiceControllerStatus.Running:
return 1;
default:
return 2;
}
}
}
public void QueryService()
{
service.Refresh();
}
}
and collection of Components, declared in another class:
public class Motivation
{
// Downloaded data
...
private ObservableCollection<Component> components;
public ObservableCollection<Component> Components
{
get { return components; }
}
public bool CheckServices()
{
bool changed = false;
foreach (Component C in components)
{
int prevStatus = C.ServiceStatus;
C.QueryService();
if (prevStatus != C.ServiceStatus)
changed = true;
}
return changed;
}
This components list displayed in WPF DataGrid. My idea: green background color for running services, red - for stopped. Works fine, but only on start. CheckServices() called by timer, and if returned value is True, i want to rerender my grid, respect to new service statuses. Here is XAML:
<Style x:Key="ServiceStateStyle" TargetType="z:DataGridRow">
<Setter Property="Background" Value="Gray" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ServiceStatus}" Value="0">
<Setter Property="Background" Value="LightCoral" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=ServiceStatus}" Value="1">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
<z:DataGrid Grid.Row="0"
Grid.ColumnSpan="4"
AutoGenerateColumns="False"
x:Name="DataGridComponents"
ItemContainerStyle="{DynamicResource ServiceStateStyle}">
<z:DataGrid.Columns>
<z:DataGridTextColumn IsReadOnly="True"
Header="Component" Width="80"
Binding="{Binding Path=DisplayName}"/>
</z:DataGrid.Columns>
</z:DataGrid>
Should i call any method explicit to invalidate DataGrid? I have tried with InvalidateProperty, InvalidateVisual, GetBindingExpression(ItemContainerStyleProperty).UpdateTarget(), but nothing work. Can anyone help?
The Component class must implement the INotifyPropertyChanged and raise the event when some of it's property change.

Categories