WPF Binding to collection with constraint - c#

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>

Related

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 bind only 'some' list items to the ListView in WPF?

I have a model dfined like this:
class MyImage
{
public string imagePath {get; set;}
public bool isIncluded {get; set;}
}
and the list to hold these models:
public List<MyImage> myImages {get; set;}
Now, I know how to bind this entire list to the ListView, but what I want is to have ListView displaying only the images of those models in which the property 'isIncluded' is set to true.
My current solution is to have another List<MyImages> in which I copy only those that are included, but I was wondering if there is any better solution.
You could simply hide the items to be excluded in the ListView in the view by defining an ItemContainerStyle with a DataTrigger:
<ListView ...>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding isIncluded}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
Use a CollectionView and apply a filter...very simple
public ICollectionView SupportedDevices
{
get
{
if (Data != null)
{
return CollectionViewSource.GetDefaultView(Data);
}
else
return null;
}
}
private string _searchedText = string.Empty;
public string SearchedText
{
get { return _searchedText; }
set
{
_searchedText = value;
SupportedDevices.Filter = delegate(object obj)
{
if (string.IsNullOrEmpty(_searchedText))
return true;
DeviceInfo data = obj as DeviceInfo;
if (data == null)
return false;
return (
(data.Manufacturer.IndexOf(_searchedText, 0, StringComparison.InvariantCultureIgnoreCase) > -1) ||
data.Model.IndexOf(_searchedText, 0, StringComparison.InvariantCultureIgnoreCase) > -1
);
};
}
}
I don't think you can avoid having another list. A more elegant solution might be a LINQ query on the property:
public List<MyImage> MyImages => myImages.Where(i => i.IsIncluded).ToList();
Another more intricate approach might be a CollectionViewSource.

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>

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