OnPropertyChanged event after loading properties during runtime - c#

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>

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.

WPF DataGrid Highlighting all cells in a column that match the selected cell

I fill an ObservableCollection<> (also tried a List<>) with a custom Class. I've bound the collection to the DataGrid and defined the columns. Now I want to select a cell and have the other cells (in the same column although data in other columns won't ever match) that have the same string in it to be highlighted.
<Window.Resources>
<local:CellHighlighterConverter x:Key="cellHighlighterConverter" />
<CollectionViewSource x:Key="ScanCollectionViewSource" CollectionViewType="ListCollectionView" />
<Style x:Key="CenterCell" TargetType="TextBlock">
<Setter Property="TextBlock.TextAlignment" Value="Center" />
</Style>
<Style x:Key="CellPad" TargetType="TextBlock">
<Setter Property="Margin" Value="15,0,15,0" />
</Style>
<Style x:Key="CellHighlighterStyle" TargetType="TextBlock" >
<Setter Property="Background" Value="{Binding IsMatching, NotifyOnSourceUpdated=True, Converter={StaticResource cellHighlighterConverter}}" />
<Setter Property="TextBlock.TextAlignment" Value="Center" />
</Style>
</Window.Resources>
<Grid HorizontalAlignment="Center" >
<DataGrid x:Name="scans" DataContext="{StaticResource ScanCollectionViewSource}" ItemsSource="{Binding}" AutoGenerateColumns="False" FontFamily="Lucida Console" Margin="10" MouseUp="scans_MouseUp" >
<DataGrid.Columns>
<DataGridTextColumn Header="Device Name" Binding="{Binding Hostname}" Width="125" />
<DataGridTextColumn Header="Scan Date" Binding="{Binding ScanDate}" Width="75" ElementStyle="{StaticResource CenterCell}" />
<DataGridTextColumn Header="GUID" Binding="{Binding GUID}" Width="300" ElementStyle="{StaticResource CenterCell}" />
<DataGridTextColumn Header="MAC" Binding="{Binding MAC}" Width="105" ElementStyle="{StaticResource CellHighlighterStyle}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
Code behind
private ObservableCollection<Scan> ReadFiles()
{
scanList = new List<Scan>();
...
foreach(string file in files)
{
Scan newScan = new Scan();
...fill newScan with data
scanList.Add(newScan);
}
scanList = scanList.OrderBy(x => x.Hostname).ThenByDescending(x => x.ScanDate).ToList();
scanCollection = new ObservableCollection<Scan>(scanList);
return scanCollection;
}
Ways I've tried to accomplish this. Added a property to the class, then on mouse up, set that property if data matches (this works), then use a converter to set the background based on that data. Either the grid isn't refreshing or something else is wrong. Am I on the right track or is there a better way?
private void scans_MouseUp(object sender, MouseButtonEventArgs e)
{
string selMAC = ((Scan)((DataGrid)sender).SelectedValue).MAC;
foreach (Scan scan in scanList)//((DataGrid)sender).Items.OfType<Scan>().ToList() )
{
// compare values
if (scan.MAC == selMAC)
{
scan.IsMatch = true;
} else
{
scan.IsMatch = false;
}
//scans.Items.Refresh();
}
}
}
public class CellHighlighterConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if( (Boolean)value )
{
return new SolidColorBrush(Colors.Green);
}
return SystemColors.AppWorkspaceColor;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class Scan
{
public Scan()
{
this.IsMatch = false;
}
public string Hostname { get; set; }
public string ScanDate { get; set; }
public string GUID { get; set; }
public string MAC { get; set; }
public bool IsMatch { get; set; }
}
Scan should implement INotifyPropertyChanged interface and raise notificattion for IsMatch property:
private bool _IsMatch;
public bool IsMatch
{
get { return _IsMatch; }
set
{
if (_IsMatch == value) return;
_IsMatch = value;
OnPropertyChanged("IsMatch");
}
}
without such notification Convert method isn't triggered even if you change IsMatch.
other things I would fix:
Convert method should return Brush, not Color
{
return (bool)value ? Brushes.Green : SystemColors.AppWorkspaceBrush;
}
use SelectionChanged event instead of MouseUp. MouseUp can happen on the same cell multiple times, no need to search mathes each time

Update DataTemplate on PropertyChanged does not work

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>

Change Color of DataGrid

I wanted to change the color of the entire row but row is null with my current code. datagrid.Rows doesn't exist.
I want to highlight the 3rd row for example.
var row = datagrid.ItemContainerGenerator.ContainerFromItem(3) as Microsoft.Windows.Controls.DataGridRow;
row.Background = Brushes.Blue;
Try something like this:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Executed}" Value="False">
<Setter Property="Background" Value="LightCoral" />
</DataTrigger>
<DataTrigger Binding="{Binding Executed}" Value="True">
<Setter Property="Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
In this case I am using caliburn micro to bind the background color depending on a bool within my row (used bool? to remain white until bool is changed).
This is not really the best way to change the background of a DataGridRow - you should use a Style as suggested by #David Danielewicz - but for your current approach to work you should cast the object returned from the method to a System.Windows.Controls.DataGridRow.
You should also use the ContainerFromIndex method to get a reference to the visual container for the fourth element. The third element has an index of 2.
Try this:
var row = datagrid.ItemContainerGenerator.ContainerFromIndex(2) as System.Windows.Controls.DataGridRow;
row.Background = Brushes.Blue;
Also note that for this to work, you need to wait until the containers have actually been created:
datagrid.Loaded += (ss, ee) =>
{
var row = datagrid.ItemContainerGenerator.ContainerFromIndex(2) as System.Windows.Controls.DataGridRow;
row.Background = Brushes.Blue;
};
Accessing View from code behind is a bad practice. Better use the power of MVVM:
<Window>
<Window.Resources>
<ResourceDictionary>
<Style x:Key="DataGridRowStyle" TargetType="DataGridRow">
<Setter Property="Background" Value="{Binding RowBackground}"/>
</Style>
</ResourceDictionary>
</Window.Resources>
<DataGrid ItemsSource="{Binding Records}" RowStyle="{StaticResource DataGridRowStyle}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Value}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Window>
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
public class MainWindowViewModel
{
public MainWindowViewModel()
{
Records.Add(new RecordViewModel()
{
Value = "Red",
RowBackground = new SolidColorBrush(Colors.LightCoral)
});
Records.Add(new RecordViewModel()
{
Value = "Green",
RowBackground = new SolidColorBrush(Colors.LightGreen)
});
Records.Add(new RecordViewModel()
{
Value = "Blue",
RowBackground = new SolidColorBrush(Colors.LightBlue)
});
Records[2].Value = "Not blue anymore";
Records[2].RowBackground = new SolidColorBrush(Colors.LightPink);
}
public ObservableCollection<RecordViewModel> Records { get; } = new ObservableCollection<RecordViewModel>();
}
public class RecordViewModel : INotifyPropertyChanged
{
private string _value;
private Brush _rowBG;
public event PropertyChangedEventHandler PropertyChanged;
public string Value
{
get
{
return _value;
}
set
{
_value = value;
OnPropertyChanged(nameof(Value));
}
}
public Brush RowBackground
{
get
{
return _rowBG;
}
set
{
_rowBG = value;
OnPropertyChanged(nameof(RowBackground));
}
}
private void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}

How to change Datagrid row color at runtime in wpf?

I am using WPF DataGrid and adding rows at runtime by using a class RowItem
public class RowItem //Class
{
public int Rule_ID { get; set; }
public string Rule_Desc { get; set; }
public Int64 Count_Of_Failure { get; set; }
}
adding row at run time like :
dgValidateRules.Items.Add(new RowItem() { Rule_ID = ruleID, Rule_Desc = ruleDesc, Count_Of_Failure = ttlHodlings });
Used the below Loading Row event code for changing color of the datagrid row. But its not working.
private void dgValidateRules_LoadingRow(object sender, DataGridRowEventArgs e)
{
for (int i = 1; i < dgValidateRules.Items.Count; i++)
{
if (((RowItem)dgValidateRules.Items[i]).Count_Of_Failure == 0)
e.Row.Foreground = new SolidColorBrush(Colors.Black);
else
e.Row.Foreground = new SolidColorBrush(Colors.Red);
}
}
Can anybody tell me the solution?
Because it row event there it is the worng place to do it, here you can place a condition about the row:
private void table_LoadingRow(object sender, DataGridRowEventArgs e)
{
if (((MyData)e.Row.DataContext).Module.Trim().Equals("SomeText"))
{
e.Row.Foreground = new SolidColorBrush(Colors.Red);
}
}
You can do with a DataTrigger or Converter
<DataGrid ItemsSource="{Binding YourItemsSource}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Count_Of_Failure}" Value="0">
<Setter Property="Foreground" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Count_Of_Failure}" Value="1">
<Setter Property="Foreground" Value="Green"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>

Categories