Dependency property for ItemsSource column to set DataTrigger condition - wpf-controls

How would I properly bound to ItemsSource column via Dependency property of control (Datagrid), in order to set It's DataTrigger working?
My goal that works without dependency property:
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding NAME}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
And this is how I want It to work:
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding HideRow, ElementName =_myGrid}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
My dependency property :
public static DependencyProperty HideRowProperty =
DependencyProperty.Register("HideRow", typeof(PersonsModel), typeof(My_DataGrid),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
///<summary>Property for hiding rows, based on column name</summary>
public PersonsModel HideRow
{
get { return (PersonsModel)GetValue(HideRowProperty); }
set { SetValue(HideRowProperty, value); }
}
And this is how I try to bind control in XAML:
<ctl:My_DataGrid ItemsSource="{Binding Persons}" HideRow="{Binding Persons.NAME}">
More explanation: ItemsSource Persons is ObservableCollection of PersonsModel (which is type of Dependency property).
I'm getting BindingExpression path error: 'NAME' property not found on 'object' ''ObservableCollection`1' error.

Based on your comments, your goal is to tell DataGrid to hide the row that has some property value == null. Hence, you have to assign the property's name to HideRow property and use a converter which uses reflection to get the property value and affect the style trigger.
You have 2 options, the first one is simpler and you don't need the HideRow property at all:
Change RowStyle a bit, Here you will pass the name of the property as a ConverterParameter (so it might be Name, Age, etc...)
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding ., Converter={StaticResource NullPropertyToBoolConverter}, ConverterParameter=Name}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
Where NullPropertyToBoolConverter is
public class NullPropertyToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is PersonsModel person && parameter is string propertyName)
{
return typeof(PersonsModel).GetProperty(propertyName)?.GetValue(person, null) == null;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The converter is defined in resources of one of DataGrid's parents (or in Application.xaml)
<converters:NullPropertyToBoolConverter x:Key="NullPropertyToBoolConverter" />
It is getting the value of Name, and return true if it was null, and this will cause the row visibility to be Collapsed.
This option is suitable because in both options you have to define RowStyle but here you don't need to define a dependency property.
Here you would change HideRow type to string instead of PersonsModel, and use it like this:
<ctl:My_DataGrid ItemsSource="{Binding Persons}" HideRow="Name" >
So, it might be Name, Age, etc...
You'd define <DataGrid.RowStyle> similar to this
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource NullPropertyToBoolConverter2}">
<Binding Path="DataContext" RelativeSource="{RelativeSource Mode=Self}" />
<Binding Path="HideRow" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=DataGrid}" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
and define the Converter to return Collapse if property's value is null, otherwise Visible.
public class NullPropertyToBoolConverter2 : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is Obj obj && values[1] is string str)
{
return typeof(Obj).GetProperty(str)?.GetValue(obj, null) == null ? Visibility.Collapsed : Visibility.Visible;
}
return Visibility.Visible;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Related

UWP, Apply a converter to a ListView's item when the ListView's items source is a collection of strings

I have the following situation. I want to show a list of strings. In order to achieve that, I have a ListView bound to a collection of strings. In this collection, there are some empty strings. What I want is to show the following text when a empty string is present: "-empty-". This is what I got so far (the source code is for demonstration purpose only):
EmptyStringConverter.cs
public class EmptyStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string && string.IsNullOrWhiteSpace((string)value))
{
return "-empty-";
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
MainPage.xaml
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:EmptyStringConverter x:Key="EmptyStringConverter" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="ListView">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource EmptyStringConverter}}" Margin="0,0,0,5" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var source = new[] { "", "String 1", "String 2" };
ListView.ItemsSource = source;
}
}
When a put a breakpoint in the Convert method in EmptyStringConverter class, the method is called in every single item except in the empty string. How can I achieve what I want?
Well the issue was in the place I'd check the last. It's your Legacy Binding that's causing the issue. I tried a code snippet myself and then I replaced it with yours piece by piece. The below line uses legacy binding
Text="{Binding Converter={StaticResource EmptyStringConverter}}"
Since you are using UWP you can switch to a Compile time binding that'll fix your problem your modified ListView XAML would be:
<ListView x:Name="ListView" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<TextBlock Text="{x:Bind Converter={StaticResource EmptyStringConverter},Mode=OneTime}" Margin="0,0,0,5" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Please pay attention at two things:
the Text section of the Textbox in the DataTemplate, it uses x:Bind instead of Binding. Please do note that compile time binding is a oneTime binding by default (unless you explicitly mention the Mode) where as the Legacy Binding is a OneWay binding by default. For More information on compile time binding follow: xBind markup extension.
If you notice the DataTemplate declaration holds a DataType property, that helps the compile time binder to know what kind of data is it expecting. For more information on DataType follow: xBind markup extension.
All that being said, I would highly recommend using the Data Binding approach instead of the ListView.ItemSource=source as with the new Compile Time binding, alot of conversions are handled by the binding engine itself leading to less code and effort on your end. I've put up a sample on github for the same you can check it out: EmptyStringDemo

Use MVVM With UserControl

I'm trying to make a UserControl which has it's own Viewmodel.
I used dp to pass parameter from View to my UserControl.
public object SelectedItemCliEnt
{
get { return (object)GetValue(SelectedItemEntProperty); }
set { SetValue(SelectedItemEntProperty, value); }
}
public static readonly DependencyProperty SelectedItemEntProperty =
DependencyProperty.Register("SelectedItemCliEnt", typeof(object),
typeof(UCComboBoxClient), new PropertyMetadata(null));
Now, in my user control I bound the datagrid SelectedItem to this property:
<DataGrid x:Name="dtg_entite"
ItemsSource="{Binding CliColl, Source={StaticResource vm}}">
<DataGrid.SelectedItem>
<MultiBinding Converter="{StaticResource MultiBinding}"
Mode="TwoWay">
<Binding Path="SelectedCli"
Source="{StaticResource vm}"
Mode="TwoWay" />
<Binding Path="SelectedItemCliEnt"
Mode="TwoWay"
RelativeSource="{RelativeSource AncestorType={x:Type UC:UCComboBoxClient}, Mode=FindAncestor}" />
</MultiBinding>
</DataGrid.SelectedItem>
</DataGrid>
I also bound the selectedItem to a property (SelectedCli) in my ViewModel which I can access by using a resources, it allow me to modify the content of the datagrid:
<ViewModel:VMUCCBClient x:Key="vm" />
How can I notify to SelectedItemCliEnt the changes in SelectedCli from the UserControl's ViewModel?

Display image based on combobox item selection in wpf

I have one combo box in which I have some items like:
Item1
Item2
Item3
Corresponding every item there is image like item1 has image img1.jpg, item2 has image img2.jpg and item3 has image img3.jpg. When we select item from combox it will show their corresponding image in label.
I got answer of my question and here it's:
<xmlns:local="clr-namespace:ImageMVVM_Learning"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:EnumToImageConverter x:Key="conv"/>
</Window.Resources>
<Grid>
<StackPanel>
<ComboBox x:Name="combo" ItemsSource="{Binding MyProperty}"/>
<Image Source="{Binding ElementName=combo,Path=SelectedValue,Converter={StaticResource conv}}"/>
</StackPanel>
</Grid>
</Window>
Do this in your viewmodel class:
public enum MyEnum
{
A,
B,
C
}
public class EnumToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
switch ((MyEnum)value)
{
case MyEnum.A:
return new BitmapImage(new Uri(#"Images\A.png", UriKind.Relative));
case MyEnum.B:
return new BitmapImage(new Uri(#"Images\B.png", UriKind.Relative));
}
}
return new BitmapImage(new Uri(#"Images\A.png", UriKind.Relative));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}

Set row color of Telerik Grid

I have used Telerik RadGrid to build a grid. The grid itself works but it is databound to a SQL database. I am trying to display rows with different colors.
Here is an example of what I am trying to do:
Here is what I have so far:
protected void SummaryGrid_ItemDataBound(object sender, GridItemEventArgs e)
{
if (e.Item is GridDataItem)
{
GridItem dataItem = e.Item;
if (dataItem["Red"].Text = "Red")
{
dataItem.BackColor = Color.Red;
}
}
}
Any help with this would be great.
Try the following code to change the color based on a particular value.
protected void RadGrid1_ItemDataBound(object sender, GridItemEventArgs e)
{
if (e.Item is GridDataItem)
{
GridDataItem dataItem = e.Item;
if (dataItem["Size"].Text == "1")
{
dataItem.BackColor = Drawing.Color.Red;
}
}
You can use style triggers to accomplish this.
App.xaml
<Style BasedOn="{StaticResource GridViewRowStyle}" TargetType="telerik:GridViewRow">
<Style.Triggers>
<DataTrigger Binding="{Binding YourObject.Size Converter={StaticResource ColorConverter}}" Value="Red">
<Setter Property="Background" Value="Red" />
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
<DataTrigger Binding="{Binding YourObject.Size Converter={StaticResource ColorConverter}}" Value="Green">
<Setter Property="Background" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
ColorConverter.cs
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
if ((int)value > 100)
return "Red";
else
return "Green";
}
else
return "Default";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// TODO: Implement this method
throw new NotImplementedException();
}
}
Figured it out and its really simple actually, just going to say if the text equals what you need it to be then its a red row or if its something else then its whatever other color. Really easy but a lot of people have had this issue so hopefully this helps others.
if (e.Item is GridDataItem)
{
var item = (GridDataItem)e.Item;
if (item["Type"].Text == "RedRow")
{
item.BackColor = Color.Red;
}
else if(item["Type"].Text == "OrangeRow")
{
item.BackColor = Color.Orange;
}
}

What are the best practices to dynamically create views and add controls to view using MVVM pattern

Can anyone give out the best practices for playing around with controls at runtime such as creating a new view,adding views inside a view,adding controls to containers using MVVM pattern without breaking mvvm pattern??
I am using MVVMlight toolkit..
please help me out in this regard..
Thanks in advance......
This post discusses the strategies for creating views (dialogs) from your view models.
Edit:
From your comment I take it that you got an user interface that has an add and delete button. The add button should add an item (type ?) to a ItemsControl ... hope that's correct.
So, how would I do this, well I would create a view model that has an ObservableCollecion<ItemViewModel>. The ItemViewModle is the view mode that represents the item that should be added to the ItemsControl (so in your case the view model backing your "rangeView").
Then I would add two commands that handle the addition and deletion of items. Both commands just add/remove ItemViewModels from your collection.
To show the items in the view I would bind the ItemControl.ItemsSource property to the collection in your main view model (i.e. the one holding the ItemViewModel instances). The I would supply an ItemTemplate to render the items on the screen.
Ok, here is an example of what I think you are trying to do (at least conceptionally). Complete Source Code here. In the example I used a ListBox as it allows me easily to determine which item is selected, this depends on your szenario. Also note that you have complete freedom to customize the Template, the ItemPanelTemplate, and DataTemplate to fit your needs. You can even use this approacht to create PanoramaPages in WP7!
2nd edit: ItemsControl does not have a SelectedItem property. To get to it you have to use a control inheriting from Selector (e.g. a ListBox as I did) or you can use the Selector directly.
ItemViewModel:
public class ItemViewModel : ViewModelBase
{
#region [Name]
public const string NamePropertyName = "Name";
private string _name = null;
public string Name {
get {
return _name;
}
set {
if (_name == value) {
return;
}
var oldValue = _name;
_name = value;
RaisePropertyChanged(NamePropertyName);
}
}
#endregion
}
MainViewModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel() {
if (IsInDesignMode) {
this.Items = new ObservableCollection<ItemViewModel>(Enumerable.Range(0, 10).Select((x, i) => new ItemViewModel() { Name = "Design Time Item " + i }));
} else {
// Code runs "for real"
}
}
#region [AddCommand]
private RelayCommand _addCommand;
public RelayCommand AddCommand {
get {
return _addCommand ?? (_addCommand = new RelayCommand(
() => {
this.Items.Add(new ItemViewModel() { Name = "New item - " + DateTime.Now });
}
));
}
}
#endregion
#region [DeleteCommand]
private RelayCommand _deleteCommand;
public RelayCommand DeleteCommand {
get {
return _deleteCommand ?? (_deleteCommand = new RelayCommand(
() => {
this.Items.Remove(this.SelectedItem);
},
() => { return this.SelectedItem != null; }
));
}
}
#endregion
#region [Items]
public const string ItemsPropertyName = "Items";
private ObservableCollection<ItemViewModel> _items = new ObservableCollection<ItemViewModel>();
public ObservableCollection<ItemViewModel> Items {
get {
return _items;
}
set {
if (_items == value) {
return;
}
var oldValue = _items;
_items = value;
RaisePropertyChanged(ItemsPropertyName);
}
}
#endregion
#region [SelectedItem]
public const string SelectedItemPropertyName = "SelectedItem";
private ItemViewModel _selectedItem = null;
public ItemViewModel SelectedItem {
get {
return _selectedItem;
}
set {
if (_selectedItem == value) {
return;
}
var oldValue = _selectedItem;
_selectedItem = value;
RaisePropertyChanged(SelectedItemPropertyName);
// important in SL to notify command that can execute has changed !
this.DeleteCommand.RaiseCanExecuteChanged();
}
}
#endregion
}
MainPage.xaml
<UserControl
x:Class="MvvmLight1.MainPage"
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"
mc:Ignorable="d"
Height="300"
Width="300"
DataContext="{Binding Main, Source={StaticResource Locator}}"
>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<!-- we are dealing with ItemViewModels now -->
<Border BorderThickness="0,0,0,1" BorderBrush="Gray" Padding="10,5">
<TextBlock Text="{Binding Name}"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Margin="5,10" Content="Add" Command="{Binding AddCommand}" Width="70"/>
<Button Margin="5,10" Content="Delete" Command="{Binding DeleteCommand}" Width="70"/>
</StackPanel>
</Grid>
</UserControl>

Resources