Well, this may be an easy question but I am not able to find a solution for this.
I have a DataTrigger as
<DataTrigger Binding="{Binding Quantity}" Value="0">
Now I want to bind Value to a variable myVariable. So if the value of myVariable changes, the Value property of the DataTrigger changes too.
I have tried setting Binding but it I guess its not possible to set it. Is there any other method by which I can set this Value dynamically.
EDIT : I tried creating a data trigger of my own. But I am still not able to get things working.
This is the code :
DataTrigger d = new DataTrigger();
d.Binding = new Binding("Quantity");
d.Value = 1;
Setter s = new Setter(BackgroundProperty, Brushes.Red);
d.Setters.Add(s);
Style st = new Style(typeof(DataGridRow));
st.Triggers.Add(d);
this.Resources.Add(this.Resources.Count, st);
I want to use the above code in place of following xaml
<Page.Resources>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Quantity}" Value="1">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Page.Resources>
Thanks.
To my understanding of your problem, you are trying to find a way to set the Value of your DataTrigger according to the value of one of the properties of your view model. So here I have a solution.
Here is the View Model
public class ViewModel : INotifyPropertyChanged
{
private string _text;
private string _candidateValue;
public string Text
{
get
{
return this._text;
}
set
{
this._text = value;
if (null != PropertyChanged)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
}
public string CandidateValue
{
get
{
return this._candidateValue;
}
set
{
this._candidateValue = value;
if (null != PropertyChanged)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
And you need a IValueConverter in your binding of DataTrigger
public class ValueConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty CandidateValueProperty = DependencyProperty.Register("CandidateValue", typeof(string), typeof(ValueConverter));
public string CandidateValue
{
get { return (string)GetValue(CandidateValueProperty); }
set { SetValue(CandidateValueProperty, value); }
}
public ValueConverter()
: base()
{
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (null != value)
{
if (value.ToString() == this.CandidateValue)
return true;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
And the xaml is quite simple
<TextBox x:Name="TextBox" Text="{Binding Text,UpdateSourceTrigger=PropertyChanged}">
</TextBox>
You need to create your datatrigger in code behind.
public MainWindow()
{
InitializeComponent();
//The view model object
ViewModel vm = new ViewModel();
vm.CandidateValue = "1";
this.DataContext = vm;
//create data trigger object for TextBox
DataTrigger d = new DataTrigger();
//create binding object for data trigger
Binding b = new Binding("Text");
ValueConverter c = new ValueConverter();
b.Converter = c;
//create binding object for ValueConverter.CandidateValueProperty
Binding convertBinding = new Binding("CandidateValue");
convertBinding.Source = vm;
BindingOperations.SetBinding(c, ValueConverter.CandidateValueProperty, convertBinding);
d.Binding = b;
d.Value = true;
Setter s = new Setter(TextBox.ForegroundProperty, Brushes.Red);
d.Setters.Add(s);
Style st = new Style(typeof(TextBox));
st.Triggers.Add(d);
this.TextBox.Style = st;
}
The trick here is that the ValueConverter has a dependency property named CandidateValueProperty. And this property is bind to the CandidateValue of view model. So the foreground of the TextBox will be red when the input value equals to CandidateValue of view model.
Related
I have got a WPF datagrid and I want diffrent cell colours according to values. I have got below code on my xaml
Style TargetType="DataGridCell"
but instead of selecting a cell only is selecting all row? What am I missing?
If you try to set the DataGrid.CellStyle the DataContext will be the row, so if you want to change the colour based on one cell it might be easiest to do so in specific columns, especially since columns can have varying contents, like TextBlocks, ComboBoxes and CheckBoxes. Here is an example of setting all the cells light-green where the Name is John:
<DataGridTextColumn Binding="{Binding Name}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Text" Value="John">
<Setter Property="Background" Value="LightGreen"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
You could also use a ValueConverter to change the colour.
public class NameToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string input = (string)value;
switch (input)
{
case "John":
return Brushes.LightGreen;
default:
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
Usage:
<Window.Resources>
<local:NameToBrushConverter x:Key="NameToBrushConverter"/>
</Window.Resources>
...
<DataGridTextColumn Binding="{Binding Name}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="{Binding Name, Converter={StaticResource NameToBrushConverter}}"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
Yet another option is to directly bind the Background to a property which returns the respectively coloured brush. You will have to fire property change notifications in the setters of properties on which the colour is dependent.
e.g.
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged(nameof(Name));
OnPropertyChanged(nameof(NameBrush));
}
}
}
public Brush NameBrush
{
get
{
switch (Name)
{
case "John":
return Brushes.LightGreen;
default:
break;
}
return Brushes.Transparent;
}
}
If you need to do it with a set number of columns, H.B.'s way is best. But if you don't know how many columns you are dealing with until runtime, then the below code [read: hack] will work. I am not sure if there is a better solution with an unknown number of columns. It took me two days working at it off and on to get it, so I'm sticking with it regardless.
C#
public class ValueToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int input;
try
{
DataGridCell dgc = (DataGridCell)value;
System.Data.DataRowView rowView = (System.Data.DataRowView)dgc.DataContext;
input = (int)rowView.Row.ItemArray[dgc.Column.DisplayIndex];
}
catch (InvalidCastException e)
{
return DependencyProperty.UnsetValue;
}
switch (input)
{
case 1: return Brushes.Red;
case 2: return Brushes.White;
case 3: return Brushes.Blue;
default: return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
XAML
<UserControl.Resources>
<conv:ValueToBrushConverter x:Key="ValueToBrushConverter"/>
<Style x:Key="CellStyle" TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource ValueToBrushConverter}}" />
</Style>
</UserControl.Resources>
<DataGrid x:Name="dataGrid" CellStyle="{StaticResource CellStyle}">
</DataGrid>
This may be of help to you. It isn't the stock WPF datagrid however.
I used DevExpress with a custom ColorFormatter behaviour. I couldn't find anything on the market that did this out of the box. This took me a few days to develop. My code attaached below, hopefully this helps someone out there.
Edit: I used POCO view models and MVVM however you could change this to not use POCO if you desire.
Viewmodel.cs
namespace ViewModel
{
[POCOViewModel]
public class Table2DViewModel
{
public ITable2DView Table2DView { get; set; }
public DataTable ItemsTable { get; set; }
public Table2DViewModel()
{
}
public Table2DViewModel(MainViewModel mainViewModel, ITable2DView table2DView) : base(mainViewModel)
{
Table2DView = table2DView;
CreateTable();
}
private void CreateTable()
{
var dt = new DataTable();
var xAxisStrings = new string[]{"X1","X2","X3"};
var yAxisStrings = new string[]{"Y1","Y2","Y3"};
//TODO determine your min, max number for your colours
var minValue = 0;
var maxValue = 100;
Table2DView.SetColorFormatter(minValue,maxValue, null);
//Add the columns
dt.Columns.Add(" ", typeof(string));
foreach (var x in xAxisStrings) dt.Columns.Add(x, typeof(double));
//Add all the values
double z = 0;
for (var y = 0; y < yAxisStrings.Length; y++)
{
var dr = dt.NewRow();
dr[" "] = yAxisStrings[y];
for (var x = 0; x < xAxisStrings.Length; x++)
{
//TODO put your actual values here!
dr[xAxisStrings[x]] = z++; //Add a random values
}
dt.Rows.Add(dr);
}
ItemsTable = dt;
}
public static Table2DViewModel Create(MainViewModel mainViewModel, ITable2DView table2DView)
{
var factory = ViewModelSource.Factory((MainViewModel mainVm, ITable2DView view) => new Table2DViewModel(mainVm, view));
return factory(mainViewModel, table2DView);
}
}
}
IView.cs
namespace Interfaces
{
public interface ITable2DView
{
void SetColorFormatter(float minValue, float maxValue, ColorScaleFormat colorScaleFormat);
}
}
View.xaml.cs
namespace View
{
public partial class Table2DView : ITable2DView
{
public Table2DView()
{
InitializeComponent();
}
static ColorScaleFormat defaultColorScaleFormat = new ColorScaleFormat
{
ColorMin = (Color)ColorConverter.ConvertFromString("#FFF8696B"),
ColorMiddle = (Color)ColorConverter.ConvertFromString("#FFFFEB84"),
ColorMax = (Color)ColorConverter.ConvertFromString("#FF63BE7B")
};
public void SetColorFormatter(float minValue, float maxValue, ColorScaleFormat colorScaleFormat = null)
{
if (colorScaleFormat == null) colorScaleFormat = defaultColorScaleFormat;
ConditionBehavior.MinValue = minValue;
ConditionBehavior.MaxValue = maxValue;
ConditionBehavior.ColorScaleFormat = colorScaleFormat;
}
}
}
DynamicConditionBehavior.cs
namespace Behaviors
{
public class DynamicConditionBehavior : Behavior<GridControl>
{
GridControl Grid => AssociatedObject;
protected override void OnAttached()
{
base.OnAttached();
Grid.ItemsSourceChanged += OnItemsSourceChanged;
}
protected override void OnDetaching()
{
Grid.ItemsSourceChanged -= OnItemsSourceChanged;
base.OnDetaching();
}
public ColorScaleFormat ColorScaleFormat { get; set;}
public float MinValue { get; set; }
public float MaxValue { get; set; }
private void OnItemsSourceChanged(object sender, EventArgs e)
{
var view = Grid.View as TableView;
if (view == null) return;
view.FormatConditions.Clear();
foreach (var col in Grid.Columns)
{
view.FormatConditions.Add(new ColorScaleFormatCondition
{
MinValue = MinValue,
MaxValue = MaxValue,
FieldName = col.FieldName,
Format = ColorScaleFormat,
});
}
}
}
}
View.xaml
<UserControl x:Class="View"
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:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
xmlns:ViewModels="clr-namespace:ViewModel"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:behaviors="clr-namespace:Behaviors"
xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking"
DataContext="{dxmvvm:ViewModelSource Type={x:Type ViewModels:ViewModel}}"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="800">
<UserControl.Resources>
<Style TargetType="{x:Type dxg:GridColumn}">
<Setter Property="Width" Value="50"/>
<Setter Property="HorizontalHeaderContentAlignment" Value="Center"/>
</Style>
<Style TargetType="{x:Type dxg:HeaderItemsControl}">
<Setter Property="FontWeight" Value="DemiBold"/>
</Style>
</UserControl.Resources>
<!--<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="" Command="{Binding OnLoadedCommand}"/>
</dxmvvm:Interaction.Behaviors>-->
<dxg:GridControl ItemsSource="{Binding ItemsTable}"
AutoGenerateColumns="AddNew"
EnableSmartColumnsGeneration="True">
<dxmvvm:Interaction.Behaviors >
<behaviors:DynamicConditionBehavior x:Name="ConditionBehavior" />
</dxmvvm:Interaction.Behaviors>
<dxg:GridControl.View>
<dxg:TableView ShowGroupPanel="False"
AllowPerPixelScrolling="True"/>
</dxg:GridControl.View>
</dxg:GridControl>
</UserControl>
In my case convertor must return string value. I don't why, but it works.
*.xaml (common style file, which is included in another xaml files)
<Style TargetType="DataGridCell">
<Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource ValueToBrushConverter}}" />
</Style>
*.cs
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Color color = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);
return "#" + color.Name;
}
Just put instead
<Style TargetType="{x:DataGridCell}" >
But beware that this will target ALL your cells (you're aiming at all the objects of type DataGridCell )
If you want to put a style according to the cell type, I'd recommend you to use a DataTemplateSelector
A good example can be found in Christian Mosers' DataGrid tutorial:
http://www.wpftutorial.net/DataGrid.html#rowDetails
Have fun :)
// Example: Adding a converter to a column (C#)
Style styleReading = new Style(typeof(TextBlock));
Setter s = new Setter();
s.Property = TextBlock.ForegroundProperty;
Binding b = new Binding();
b.RelativeSource = RelativeSource.Self;
b.Path = new PropertyPath(TextBlock.TextProperty);
b.Converter = new ReadingForegroundSetter();
s.Value = b;
styleReading.Setters.Add(s);
col.ElementStyle = styleReading;
Based on the answer by 'Cassio Borghi'. With this method, there is no need to change the XAML at all.
DataGridTextColumn colNameStatus2 = new DataGridTextColumn();
colNameStatus2.Header = "Status";
colNameStatus2.MinWidth = 100;
colNameStatus2.Binding = new Binding("Status");
grdComputer_Servives.Columns.Add(colNameStatus2);
Style style = new Style(typeof(TextBlock));
Trigger running = new Trigger() { Property = TextBlock.TextProperty, Value = "Running" };
Trigger stopped = new Trigger() { Property = TextBlock.TextProperty, Value = "Stopped" };
stopped.Setters.Add(new Setter() { Property = TextBlock.BackgroundProperty, Value = Brushes.Blue });
running.Setters.Add(new Setter() { Property = TextBlock.BackgroundProperty, Value = Brushes.Green });
style.Triggers.Add(running);
style.Triggers.Add(stopped);
colNameStatus2.ElementStyle = style;
foreach (var Service in computerResult)
{
var RowName = Service;
grdComputer_Servives.Items.Add(RowName);
}
<DataGridTextColumn Binding="{Binding Name}" Header="Name" Foreground="White">
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="{Binding color}" />
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
In xaml.cs file
SolidColorBrush color=new SolidColorBrush(Colors.Red);
To do this in the Code Behind (VB.NET)
Dim txtCol As New DataGridTextColumn
Dim style As New Style(GetType(TextBlock))
Dim tri As New Trigger With {.Property = TextBlock.TextProperty, .Value = "John"}
tri.Setters.Add(New Setter With {.Property = TextBlock.BackgroundProperty, .Value = Brushes.Green})
style.Triggers.Add(tri)
xtCol.ElementStyle = style
My propblem is that DataGrid sends new value set by user with GUI only when the selected line is changed. When I modify value in form and press "Enter" to confirm input, everything works fine (because "Enter" moves selection to next line). But when I modify value in cell and press with mouse to any other cell in current row, new value is assigned to cell but not sent to binded object. In debugger I see that converter's ConvertBack method is called but Field.Value property setter is not! Field.Value property setter breakpoint fires only after row selection change. How can I force my Model object value change after cell edit finish, not row selection changed?
DataGrid's style:
<Style x:Key="ElementDataGridStyle" TargetType="DataGrid">
<Setter Property="AutoGenerateColumns" Value="False"/>
<Setter Property="HeadersVisibility" Value="Column"/>
<Setter Property="SelectionUnit" Value="Cell"/>
<Setter Property="CellStyle">
<Setter.Value>
<Style TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsReadOnly" Value="true">
<Setter Property="Background" Value="LightGray"/>
</Trigger>
</Style.Triggers>
<EventSetter Event="Loaded" Handler="ElementDataGridStyleCellStyle_Loaded"/>
</Style>
</Setter.Value>
</Setter>
</Style>
Code that binds DataGrid to my Model
private void SetDataGridContent(DataGrid grid, Core.Item.ItemCollection collection)
{
grid.Columns.Clear();
if (collection != null)
{
for (int i = 0; i < collection.FieldProps.Count; i++)
{
Core.FieldProp prop = collection.FieldProps[i];
if (prop.VisualName != null)
{
MultiBinding multiBinding = new MultiBinding()
{
Converter = new Visual.FieldValueConverter()
};
multiBinding.Bindings.Add(new Binding($"Fields[{i}]"));
multiBinding.Bindings.Add(new Binding($"Fields[{i}].Value"));
DataGridTextColumn column = new DataGridTextColumn
{
Binding = multiBinding,
Header = prop.VisualName,
IsReadOnly = !prop.UIEditable,
};
SetColumnFieldIndex(column, i);
grid.Columns.Add(column);
}
}
}
grid.ItemsSource = collection;
}
Fields - is ObservableCollection successor
sealed class FieldCollection : ObservableCollection<Field>
Field class implements INotifyPropertyChanged interface
public class Field : INotifyPropertyChanged
{
private string _value;
public string Value
{
get => _value;
set
{
if (_value != value)
{
Modify(value);
NotifyPropertyChanged();
}
}
}
private void Modify(string value)
{
_value = value;
}
protected void NotifyPropertyChanged([CallerMemberName]string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Value converter looks like this:
class FieldValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
object[] result = new object[2];
result[0] = Binding.DoNothing; //DependencyProperty.UnsetValue;
result[1] = value;
return result;
}
}
I have a project in which I need to bind the background of a TextBox to a value in an array where the index is a property in the DataContext:
Binding backgroundBinding= new Binding();
backgroundBinding.Path = new PropertyPath($"Elements[{Index}].Value");
I've been creating the binding in the codebehind, but want to find a better and more elegant way to do it. Would I have to create a custom converter, or is there some way to reference the Index property in the XAML?
So you've got two options here. I think you're asking for the first one. I've set up two properties in my viewmodel - one for the array of colours, and then one for the index I want to use. I'm binding to them through a MultiConverter to return the correct colour from the array. This will allow you to update your selected index at runtime, and have the background change to the newly selected colour. If you just want a static index that never changes, you should use implement IValueConverter instead of a IMultiValueConverter, and then pass the index in using the ConverterParameter property.
As a side note, I've chosen to implement the array as type Color. SolidColorBrush objects are expensive and doing it this way will help reduce that cost.
public class ViewModel : INotifyPropertyChanged
{
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private Color[] _backgroundColours = new Color[] { Colors.AliceBlue, Colors.Aqua, Colors.Azure };
public Color[] BackgroundColours
{
get => _backgroundColours;
set
{
_backgroundColours = value;
OnPropertyChanged();
}
}
private int _backgroundIndex = 1;
public int ChosenIndex
{
get => _backgroundIndex;
set
{
_backgroundIndex = value;
OnPropertyChanged();
}
}
}
...
public class BackgroundConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var backgroundColours = values[0] as Color[];
var chosenIndex = (int)values[1];
return new SolidColorBrush(backgroundColours[chosenIndex]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
...
<Grid>
<Grid.DataContext>
<local:ViewModel />
</Grid.DataContext>
<Grid.Resources>
<local:BackgroundConverter x:Key="backgroundConverter"/>
</Grid.Resources>
<TextBox>
<TextBox.Background>
<MultiBinding Converter="{StaticResource backgroundConverter}">
<Binding Path="BackgroundColours" />
<Binding Path="ChosenIndex" />
</MultiBinding>
</TextBox.Background>
</TextBox>
</Grid>
I have a form, which is binded to the model. There is one standard, basic model and few children models (with additional fields).
Above the model's controls there is a radio buttons group and upon selecting one of them the forementioned additional fields appear (in this case the sentence field).
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void sentencedPersonRadioButton_Checked(object sender, RoutedEventArgs e)
{
sentenceTextBox.Visibility = Visibility.Visible;
DataContext = new SentencedPerson();
}
private void personRadioButton_Checked(object sender, RoutedEventArgs e)
{
sentenceTextBox.Visibility = Visibility.Hidden;
DataContext = new Person();
}
}
Lets say there is a Person and SentencedPerson:
public class Person: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private String name;
public String Name
{
get
{
return name;
}
set
{
if (value == name)
{
return;
}
name = value;
OnPropertyChanged("Name");
}
}
}
public class SentencedPerson : Person
{
private String sentence;
public String Sentence
{
get
{
return sentence;
}
set
{
if (value == sentence)
{
return;
}
sentence = value;
OnPropertyChanged("Sentence");
}
}
}
What is the proper way to design such a connections? Adding new 'checked' event handlers feels so cumbersome... I heard about MVVM pattern in which there would be some kind of PersonContext with Person and SentencedPerson props inside. But it does not change the need of 'checked' events.
Also know there is a problem because the values of common fields are after setting the new DataContext.
This is a quite broad question but I will give you some pointers.
MVVM is the recommended design pattern to use when building XAML based applications.
You could create a view model class with a "CurrentSelectedContent" property of type object or Person and an enum property that you bind the RadioButton to.
Please refer to the following link for more information and an example of how to bind a RadioButton to an enum source property using MVVM:
How to bind RadioButtons to an enum?
Once you have done this you could set the value of the "CurrentSelectedContent" property based on the radio button selection in the setter of the enum source property in the view model:
private MyLovelyEnum _enum;
public MyLovelyEnum VeryLovelyEnum
{
get
{
return _enum;
}
set
{
_enum = value;
switch (value)
{
case MyLovelyEnum.Person:
CurrentSelectedContent = new Person();
break;
//...
}
OnPropertyChanged("VeryLovelyEnum");
}
}
Make sure that the "CurrentSelectedContent" property raises the PropertyChanged event and that the view model class implements the INotifyPropertyChanged interface.
In the view you could then use a ContentControl and bind its Content property to the "CurrentSelectedContent" property:
<ContentControl Content="{Binding Content}">
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Also make sure that you set the DataContext of the view to an instance of your view model:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
This is the rough idea on how to do this using the MVVM pattern. Instead of handling events in the code-behind of the view you bind to source properties and instead of setting the DataContext property of specific UI elements explicitly you bind the Content property of a ContentControl to an object that you create in the view model class.
Hope that helps.
You just need One model:
public class Person : INotifyPropertyChanged
{
string _name;
public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }
bool _isSentenced;
public bool IsSentenced { get { return _isSentenced; } set { _isSentenced = value; RaisePropertyChanged("IsSentenced"); } }
string _sentence;
public string Sentence { get { return _sentence; } set { _sentence = value; RaisePropertyChanged("Sentence"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
Use IsSentenced and bind the RadioButton to it. Also, check the Visibility of the TextBox displaying the Sentence string to the IsChecked property of the RadioButton, using a Visibility to Bool converter. Here is a simple example:
<Window.Resources>
<local:VisibilityToBoolConverter x:Key="VisibilityToBoolConverter"/>
</Window.Resources>
<ListBox DataContext="{Binding}" ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Name}" />
<RadioButton Content="Is sentenced to death" IsChecked="{Binding IsSentenced}" />
<DockPanel Visibility="{Binding IsSentenced , Converter={StaticResource VisibilityToBoolConverter}}">
<Label Content="Sentence: "/>
<TextBlock Text="{Binding Sentence}" />
</DockPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
in which
public class VisibilityToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value == true)
return Visibility.Visible;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((Visibility)value == Visibility.Visible)
return true;
return false;
}
}
and the ViewModel is:
public class PersonViewModel : INotifyPropertyChanged
{
public PersonViewModel()
{
Person m1 = new Person() { Name = "person 1", IsSentenced = false, Sentence = "S S S" };
Person m2 = new Person() { Name = "person 2", IsSentenced = false, Sentence = "B B B" };
Person m3 = new Person() { Name = "person 3", IsSentenced = true, Sentence = "F F F" };
_persons = new ObservableCollection<Person>() { m1, m2, m3 };
}
ObservableCollection<Person> _persons;
public ObservableCollection<Person> Persons { get { return _persons; } set { _persons = value; RaisePropertyChanged("Persons"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
your main window should set the DataContext:
public MainWindow()
{
PersonViewModel mv = new PersonViewModel();
this.DataContext = mv;
InitializeComponent();
}
Edit
If there are many states for a person, ComboBox is a more natural choice. You should have an Enum that describes the states:
public enum MyTypes
{
None,
IsA,
IsB,
IsC
}
and the Person should have a peroperty that shows the state:
public class Person : INotifyPropertyChanged
{
MyTypes _thetype;
public MyTypes TheType { get { return _thetype; } set { _thetype = value; RaisePropertyChanged("TheType"); } }
string _name;
public string Name { get { return _name; } set { _name = value; RaisePropertyChanged("Name"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
Since you need to bind the ItemsSource of your ComboBox to a list of states, one possibility is to adjust the ViewModel to have such a list:
public class PersonViewModel : INotifyPropertyChanged
{
public PersonViewModel()
{
Person m0 = new Person() { Name = "person 1", TheType = MyTypes.None };
Person m1 = new Person() { Name = "person 1", TheType = MyTypes.IsA };
Person m2 = new Person() { Name = "person 2", TheType = MyTypes.IsB };
Person m3 = new Person() { Name = "person 3", TheType = MyTypes.IsC };
_persons = new ObservableCollection<Person>() { m0, m1, m2, m3 };
_types = Enum.GetNames(typeof(MyTypes)).ToList();
}
List<string> _types;
public List<string> Types { get { return _types; } }
ObservableCollection<Person> _persons;
public ObservableCollection<Person> Persons { get { return _persons; } set { _persons = value; RaisePropertyChanged("Persons"); } }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname));
}
}
and at last, the View:
<Window.Resources>
<local:EnumToSentenceConverterx:Key="EnumToSentenceConverter"/>
<local:NoneToCollapsedConverter x:Key="NoneToCollapsedConverter"/>
<local:EnumToStringConverter x:Key="EnumToStringConverter"/>
</Window.Resources>
<ListBox Name="lb" DataContext="{Binding}" ItemsSource="{Binding Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Name}" />
<ComboBox Name="cb" ItemsSource="{Binding ElementName=lb, Path=DataContext.Types}" SelectedValue="{Binding TheType, Mode=TwoWay, Converter={StaticResource EnumToStringConverter}}" />
<DockPanel Visibility="{Binding ElementName=cb, Path=SelectedValue, Converter={StaticResource NoneToCollapsedConverter}}">
<Label Content="Sentence: " DockPanel.Dock="Left"/>
<TextBlock Text="{Binding TheType, Converter={StaticResource EnumToStringConverter}}" DockPanel.Dock="Right" VerticalAlignment="Center" />
</DockPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note that you need three converters. One sets the Visibility of The Sentence part to Collapsed, it the type is None:
public class NoneToCollapsedConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.ToString() == "None")
return Visibility.Collapsed;
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The two others are self descriptive:
public class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Enum.Parse(typeof(MyTypes), value.ToString());
}
}
and
public class EnumToSentenceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch ((MyTypes)value)
{
case MyTypes.IsA:
break;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Hope it helps.
I want to change datagrid row background color, by taking value from Database.
I have 2 value "ERROR" and "OK". If Column string value is ERROR then row color will be red and if OK then It must be Green. This value get from database by executing query. I have these values in dataset. I am not clear that how to achieve this?
I have tried below code:
<Window x:Class="stackDatagridColor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:stackDatagridColor"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<viewModels:viewmodel x:Key="viewmodel"/>
<viewModels:BrushConverter x:Key="BrushConverter"/>
</Window.Resources>
<Grid>
<StackPanel>
<DataGrid ItemsSource="{Binding Collection, Mode=TwoWay, Source={StaticResource viewmodel}, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="ERROR">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="OK">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
</StackPanel>
</Grid>
ViewModel:
public class viewmodel : INotifyPropertyChanged
{
private ObservableCollection<myItem> collection;
public ObservableCollection<myItem> Collection
{
get { return collection; }
set { collection = value; OnPropertyChanged("Collection"); }
}
public viewmodel()
{
Collection = new ObservableCollection<myItem>();
myItem item1 = new myItem { Name = "name1", Status = "OK" };
myItem item2 = new myItem { Name = "name2", Status = "ERROR" };
DispatchService.Invoke(() =>
{
Collection.Add(item1);
Collection.Add(item2);
});
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Simple Class
public class myItem
{
public string Status { get; set; }
public string Name { get; set; }
}
Dispatcher class
public static class DispatchService
{
public static void Invoke(Action action)
{
Dispatcher dispatchObject = Application.Current.Dispatcher;
if (dispatchObject == null || dispatchObject.CheckAccess())
{
action();
}
else
{
dispatchObject.Invoke(action);
}
}
}
Converter:
public class BrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string input = value as string;
switch (input)
{
case "ERROR":
return Brushes.Red;
case "OK":
return Brushes.Green;
default:
return DependencyProperty.UnsetValue;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
I just want to bind dataset column to trigger. If trigger get ERROR string then background row color change to red and vice versa.
I got My answer...No neew of such stuffs..Just used loadingrow event. This is very much usefull.
See Below
DataGridRow row = e.Row;
DataRowView rView = row.Item as DataRowView
if(rView != null && rView.Row.ItemArray[4].ToString().Contains("ERROR"))
{
e.row.Background= new SolidColorBrush(Color.Red);
}
else
{
e.row.Background= new SolidColorBrush(Color.Green);
}
Firstly Collection property should be declared in this way:
public DataSet Collection { ... }
ItemSource binding in XAML should be also changed:
ItemsSource="{Binding Path=Collection.Tables[Table1], Mode=OneWay,...
In the code above Table1 is a name of a table within a dataset you want to bind to. To test this code create a simple dataset in this way:
public viewmodel()
{
var tb = new DataTable("Table1");
tb.Columns.Add("Status");
tb.Columns.Add("Name");
tb.Rows.Add("OK", "name1");
tb.Rows.Add("ERROR", "name2");
tb.Rows.Add("ERROR", "name3");
Collection2 = new DataSet();
Collection2.Tables.Add(tb);
}