MultiBinding Color Change on Property Change - How to Clear pragmatically? - c#

Edit: I created a sample project displaying what I have done and what doesn't work. https://github.com/jmooney5115/clear-multibinding
I have a WPF application with controls (textbox, datagrid, etc). When a value changes on the control I need to indicate it by changing the background color. After saving changes the background color needs to go back to the unchanged state without reloading the control. This application is not MVVM, don't judge I inherited it.
I have the code working perfectly for changing the color using MultiBinding and a value converter. The problem is I cannot figure out how to reset the background after calling Save() in my code. I have tried doing DataContext = null and then DataContext = this but the control flickers. There has to be a better way.
Q: how can I reset the background to the unchanged state without reloading the control?
MultiBinding XAML - this works by passing a string[] to BackgroundColorConverter. string[0] is the OneTime binding. string1 is the other binding.
<TextBox.Background>
<MultiBinding Converter="{StaticResource BackgroundColorConverter}">
<Binding Path="DeviceObj.Name" />
<Binding Path="DeviceObj.Name" Mode="OneTime" />
</MultiBinding>
</TextBox.Background>
BackgroundColorConverter.cs
/// <summary>
/// https://stackoverflow.com/questions/1224144/change-background-color-for-wpf-textbox-in-changed-state
///
/// Property changed
/// </summary>
public class BackgroundColorConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var colorRed = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#FFB0E0E6");
var colorWhite = (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("White");
var unchanged = new SolidColorBrush(colorWhite);
var changed = new SolidColorBrush(colorRed);
if (values.Length == 2)
if (values[0].Equals(values[1]))
return unchanged;
else
return changed;
else
return changed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Updates
Edit: this is the multi binding for a data grid cell. If the multi binding converter returns true, set the background color to LightBlue. If false, the background is the default color.
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<!-- https://stackoverflow.com/questions/5902351/issue-while-mixing-multibinding-converter-and-trigger-in-style -->
<DataGridTextColumn.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource BackgroundColorConverterBool}">
<Binding Path="Name" />
<Binding Path="Name" Mode="OneTime" />
</MultiBinding>
</DataTrigger.Binding>
</DataTrigger>
<Setter Property="Background" Value="LightBlue"></Setter>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
.
.
.
</DataGrid.Columns>
I made this method to reset the binding of objects after saving.
/// <summary>
/// Update the data binding after a save to clear the blue that could be there when
/// a change is detected.
/// </summary>
/// <typeparam name="T">Type to search for</typeparam>
/// <param name="parentDepObj">Parent object we want to reset the binding for their children.</param>
public static void UpdateDataBinding<T>(DependencyObject parentDepObj) where T : DependencyObject
{
if (parentDepObj != null)
{
MultiBindingExpression multiBindingExpression;
foreach (var control in UIHelper.FindVisualChildren<T>(parentDepObj))
{
multiBindingExpression = BindingOperations.GetMultiBindingExpression(control, Control.BackgroundProperty);
if (multiBindingExpression != null)
multiBindingExpression.UpdateTarget();
}
}
}
Final Update
This question answers how to use MultiBinding for my purpose on DataGridCell: Update MultiBinding on DataGridCell

IHMO a MVVM solution (as Rekshino proposed) is for sure better than a not-MVVM one. The view model should take care about tracing modified data.
Anyway since you inherited this application, you have to consider how much time you need for converting the whole code and sometimes it is not possible. So in this case you can force every single multibinding to "refresh" when you save your data.
Let's suppose this is your XAML (with two or more TextBoxes):
<StackPanel>
<TextBox Margin="5" Text="{Binding DeviceObj.Name, Mode=TwoWay}">
<TextBox.Background>
<MultiBinding Converter="{StaticResource BackgroundColorConverter}">
<Binding Path="DeviceObj.Name" />
<Binding Path="DeviceObj.Name" Mode="OneTime" />
</MultiBinding>
</TextBox.Background>
</TextBox>
<TextBox Margin="5" Text="{Binding DeviceObj.Surname, Mode=TwoWay}">
<TextBox.Background>
<MultiBinding Converter="{StaticResource BackgroundColorConverter}">
<Binding Path="DeviceObj.Surname" />
<Binding Path="DeviceObj.Surname" Mode="OneTime" />
</MultiBinding>
</TextBox.Background>
</TextBox>
<Button Content="Save" Click="Button_Click" Margin="5,10,5,10" />
</StackPanel>
When you click the "Save" Button you can force MultiBindings to update their own targets in this way:
private void Button_Click(object sender, RoutedEventArgs e)
{
MultiBindingExpression multiBindingExpression;
foreach (TextBox textBox in FindVisualChildren<TextBox>(this))
{
multiBindingExpression = BindingOperations.GetMultiBindingExpression(textBox, TextBox.BackgroundProperty);
multiBindingExpression.UpdateTarget();
}
}
You can find the FindVisualChildren implementation in this answer. I hope it can help you.

You have to paste a bool Saved property to your DeviceObj and handle it, if Name or something else been changed.
ViewModel:
public class Device : INotifyPropertyChanged
{
public string Name
{
get
{
return _name;
}
set
{
if (value != _name)
{
_name = value;
Saved = false;
NotifyPropertyChanged(nameof(Name));
}
}
}
private string _name;
public bool Saved
{
get
{
return _saved;
}
set
{
if (value != _saved)
{
_saved = value;
NotifyPropertyChanged(nameof(Saved));
}
}
}
private bool _saved = true;
public void Save()
{
//Saving..
Saved = true;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
}
Converter:
public class BoolToSolColBrushConverter : IValueConverter
{
private static SolidColorBrush changedBr = new SolidColorBrush(Colors.Red);
private static SolidColorBrush unchangedBr = new SolidColorBrush(Colors.Green);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
if ((bool)value)
{
return unchangedBr;
}
}
catch (Exception)
{
}
return changedBr;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
XAML:
<TextBox Text="{Binding Name}" Background="{Binding Saved, Converter={StaticResiurce BoolToSolColBrushConverter}}" />

Related

Show a control on DataGridRow Mouse Over in WPF

It would be very convenient to show data of a specific item only when the user hovers its row in a static DataGrid. What I tried yet is: link MouseEnter and MouseLeave events to methods that will save the Index of the currently Hovered item, create a converter that will compare if the item index is the hovered item index (if yes show, otherwise hide), and finally call static INPC on the static DataGrid ItemSource, but it seems I am still missing something. (I'm coding in MVVM pattern but since this is strictly View-oriented I have no problem if the solution involves code-behind)
tl;dr: I wanna achieve this:
Here's the full code of what I did yet for easy testing :
View
<Window x:Class="MouseOverDataGridRow.MainWindow"
[...] >
<!-- Converter Section -->
<Window.Resources>
<local:HoveredRowToVisibilityConverter x:Key="HoveredRowToVisibility"/>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding MyList, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False" CanUserAddRows="False">
<!-- Events Section -->
<DataGrid.ItemContainerStyle>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseEnter" Handler="DataGridRow_MouseEnter" />
<EventSetter Event="MouseLeave" Handler="DataGridRow_MouseLeave" />
</Style>
</DataGrid.ItemContainerStyle>
<!-- Always Visible Data -->
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- On Mouse Over Only Data -->
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="Hidden Data" Visibility="{Binding Converter={StaticResource HoveredRowToVisibility}, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code-behind :
public partial class MainWindow : Window
{
// Static INPC (maybe is the problem?)
public static event PropertyChangedEventHandler StaticPropertyChanged;
private static void NotifyStaticPropertyChanged([CallerMemberName] string name = null)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(name));
}
// Hovered Row Index
public static int HoveredRowID { get; set; } = -1;
// DataGrid's ItemSource
private static ObservableCollection<int> _myList = new ObservableCollection<int>() { 1, 2, 3, 4, 5 };
public static ObservableCollection<int> MyList
{
get { return _myList; }
set { _myList = value;
NotifyStaticPropertyChanged();
}
}
// Constructor
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
// Method that retrieves the id of the Hovered Row and calls INPC. The retrieving part is working, but the View isn't updating
private void DataGridRow_MouseEnter(object sender, MouseEventArgs e)
{
DataGridRow row = (DataGridRow)sender;
if (row.Item is int)
{
int item = (int)row.Item;
HoveredRowID = MyList.IndexOf(item);
NotifyStaticPropertyChanged("MyList");
}
}
// Method that resests the Hovered Row and call INPC to hide the previously shown stuff
private void DataGridRow_MouseLeave(object sender, MouseEventArgs e)
{
HoveredRowID = -1;
NotifyStaticPropertyChanged("MyList");
}
}
// The converter
public class HoveredRowToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
int item = (int)value;
// Check if this item is the hovered item
if (MainWindow.MyList.IndexOf(item) == MainWindow.HoveredRowID)
return Visibility.Visible;
else
return Visibility.Hidden;
}
else
return Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
#Mitch's instructions are correct, Binding Button.Visibility To DataGridRow's IsMouseOver is the solution. However an important detail to be able to do that is to use a DataTrigger to do the Binding, and set the default Visbility to Hidden/Collapsed. Here I used DataTemplate.Triggers but it can also be done in Button.Triggers
<DataTemplate>
<Button x:Name="HiddenDeleteButton" Text="Delete" Visibility="Hidden" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsMouseOver, RelativeSource={RelativeSource AncestorType=DataGridRow}}" Value="True">
<Setter Property="Visibility" TargetName="HiddenDeleteButton" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
I think you can just bind Button.Visibility to IsMouseOver on DataGridRow. I don’t think there is any need to get fancy (or have anything in code behind).

Why isn't my binding for a TextBox validation rule parameter working?

I have a user control with a TextBox that needs to be validated. The validation will vary according to the value of a dependency property in the UC, so I need to pass that as a parameter. To pass a parameter I'm using Passing a data-bound value to a validation rule as a guide. However, the binding I'm using doesn't work and I don't know why. I've beat my head against it, googled everything I can think of, no joy.
Here's the code. Hopefully I've provided enough ...
In the user control I have this XAML.
<TextBox Name="ValueBox"
PreviewMouseLeftButtonUp="OnPreviewMouseLeftButtonUp"
Height="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=Height}"
BorderThickness="0"
TextAlignment="Center"
VerticalContentAlignment="Center">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=IsControlEnabled}"
Value="False">
<Setter Property="Background" Value="{StaticResource DisabledColor}"/>
</DataTrigger>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=InteractionMode}"
Value="{x:Static local:TreatmentScheduleNumberBoxUserControl+InteractionModes.Select}">
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="Cursor" Value="{x:Static Cursors.Hand}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
<TextBox.Resources>
<local:NumberBoxValueConverter x:Key="NumberBoxConverter"/>
</TextBox.Resources>
<TextBox.Text>
<tools:ConverterBindableParameter
Converter="{StaticResource NumberBoxConverter}"
ConverterParameterBinding="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TreatmentLampType}">
<!--https://social.technet.microsoft.com/wiki/contents/articles/31422.wpf-passing-a-data-bound-value-to-a-validation-rule.aspx-->
<tools:ConverterBindableParameter.Binding>
<Binding RelativeSource="{RelativeSource AncestorType=UserControl}" Path="Value" FallbackValue="3">
<Binding.ValidationRules>
<local:NumberBoxValidationRule>
<local:NumberBoxValidationRule.Wrapper>
<local:Wrapper NumberBoxUsage1="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=NumberBoxUsage
, Converter={StaticResource DebugDummyConverter, PresentationTraceSources.TraceLevel=High}}" />
</local:NumberBoxValidationRule.Wrapper>
</local:NumberBoxValidationRule>
</Binding.ValidationRules>
</Binding>
</tools:ConverterBindableParameter.Binding>
</tools:ConverterBindableParameter>
</TextBox.Text>
</TextBox>
The problem lies in this binding, where NumberBoxUsage1 is a dependency property in validation environment and NumberBoxUsage is a dependency property in the UC.
<local:Wrapper NumberBoxUsage1="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=NumberBoxUsage
, Converter={StaticResource DebugDummyConverter, PresentationTraceSources.TraceLevel=High}}" />
When it runs, NumberBoxUsage1 remains the default and isn't assigned the value of NumberBoxUsage. I can change the binding to a literal assignment and that works. I've added a dummy converter, as shown, as well as PresentationTraceSourcesbut the converter is never called and there is no trace in the Output window. Any help appreciated.
I might add that everything else in this TextBox works fine. Here's the relevant C# stuff.
Wrapper
public class Wrapper : DependencyObject
{
public NumberBoxUsages NumberBoxUsage1 {
get => (NumberBoxUsages)GetValue(NumberBoxUsage1Property);
set => SetValue(NumberBoxUsage1Property, value);
}
public static readonly DependencyProperty NumberBoxUsage1Property =
DependencyProperty.Register(nameof(NumberBoxUsage1), typeof(NumberBoxUsages), typeof(Wrapper),
new FrameworkPropertyMetadata(
NumberBoxUsages.UvPrim,
(sender, e) =>
{
var dObj = sender as Wrapper;
var x = dObj.NumberBoxUsage1;
// leave for debugging help
}
));
}
NumberBoxValidationRule
public class NumberBoxValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value == null)
{
return new ValidationResult(false, "Please enter a value");
}
if (Wrapper.NumberBoxUsage1 == NumberBoxUsages.UvbPriPct)
{
}
return ValidationResult.ValidResult;
}
public Wrapper Wrapper { get; set; }
}
ConverterBindableParameter
public class ConverterBindableParameter : MarkupExtension
{
#region Public Properties
public Binding Binding { get; set; }
public IValueConverter Converter { get; set; }
public Binding ConverterParameterBinding { get; set; }
#endregion
#region Overridden Methods
public override object ProvideValue(IServiceProvider serviceProvider)
{
var multiBinding = new MultiBinding();
multiBinding.Bindings.Add(Binding);
multiBinding.Bindings.Add(ConverterParameterBinding);
var adapter = new MultiValueConverterAdapter
{
Converter = Converter
};
multiBinding.Converter = adapter;
return multiBinding.ProvideValue(serviceProvider);
}
[ContentProperty("Converter")]
public class MultiValueConverterAdapter : IMultiValueConverter
{
public IValueConverter Converter { get; set; }
private object lastParameter;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (Converter == null) return values[0]; // Required for VS design-time
if (values.Length > 1) lastParameter = values[1];
return Converter.Convert(values[0], targetType, lastParameter, culture);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
if (Converter == null) return new object[] { value }; // Required for VS design-time
return new object[] { Converter.ConvertBack(value, targetTypes[0], lastParameter, culture) };
}
}
#endregion
}
You are missing the BindingProxy that captures the DataContext:
public class BindingProxy : System.Windows.Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new PropertyMetadata(null));
}
XAML:
<TextBox.Resources>
<local:NumberBoxValueConverter x:Key="NumberBoxConverter"/>
<local:BindingProxy x:Key="proxy" Data="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</TextBox.Resources>
...
<local:Wrapper NumberBoxUsage1="{Binding Source={StaticResource proxy}, Path=Data.NumberBoxUsage,
Converter={StaticResource DebugDummyConverter}}" />

ContentStringFormat binding doesn't refresh on propertychange

I'm having an issue causing a label to refresh when it's content doesn't change but its format does. The ContentStringFormat property is bound to the viewmodel and the property change is notified, but the label doesn't update, please find bellow a minimal reproduction exemple in code as well as a project ready to compile/run that demonstrates the issue.
Download project : https://www.dropbox.com/s/rjs1lot09uc2lgj/WPFFormatBindingRefresh.zip?dl=0
XAML :
<StackPanel>
<Label Content="{Binding FirstLabelContent}"></Label>
<Label Content="{Binding SecondLabelContent}" ContentStringFormat="{Binding SecondLabelFormatContent}"></Label>
<Button Click="Button_Click">Add "test" to all bound elements</Button>
</StackPanel>
Code behind :
public event PropertyChangedEventHandler PropertyChanged = (a,b)=> { }; // empty handler avoids checking for null when raising
public string FirstLabelContent { get; set; } = "First Label";
public string SecondLabelContent { get; set; } = "Second";
public string SecondLabelFormatContent { get; set; } = "{0} Label";
void PropertyChange(string PropertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
FirstLabelContent += " TEST";
SecondLabelFormatContent += " TEST";
PropertyChange("FirstLabelContent"); // First label correctly updates
PropertyChange("SecondLabelFormatContent"); // Second label doesn't update, expected behavior is changing the format string should cause an update
}
The Label doesn't support refreshing the ContentStringFormat through a binding.
You could use a multi converter like this:
public class MultiConverter2 : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string SecondLabelContent = values[0] as string;
string SecondLabelFormatContent = values[1] as string;
return string.Format(SecondLabelFormatContent, SecondLabelContent);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
XAML:
<StackPanel>
<StackPanel.Resources>
<local:MultiConverter2 x:Key="conv" />
</StackPanel.Resources>
<Label Content="{Binding FirstLabelContent}"></Label>
<Label>
<Label.Content>
<MultiBinding Converter="{StaticResource conv}">
<Binding Path="SecondLabelContent" />
<Binding Path="SecondLabelFormatContent" />
</MultiBinding>
</Label.Content>
</Label>
<Button Click="Button_Click">Add "test" to all bound elements</Button>
</StackPanel>

Update DataGrid Background from Binding in ViewModel

Today I found something strange (for me). If I want to update a DataGrid through a property in my ViewModel the Binding wont get notified. The special think here is (I think) the Binding is bound to another object (part of an Collection) not directly the property i change.
I have prepared some sample code for you. But first a little (Depper) explanation.
Of course it is just a sample but here I have a ViewModel with two public Properties (Items and CurrentItem). Items is a ObservableCollection and serves as the ItemsSource of my DataGrid. CurrentItem is a String which serves as indicator for a converter to set the background colour (for the grid).
I add two instances o String to my Collection and after the program is started the behaviour is as expected. The first line is green the second is white (set through the converter and the properties).
But if I change the value of CurrentItem after the program was loaded (lets say through the button) the colours wont update on my Datagrid.
If I create a breakpoint at the beginning of the converter I can see (after the loading process) the converter wont execute again so it has to be a Problem with the Binding. I think the problem is my property which is not part of the items in my Collection. The OnPropertyChange method seems to not trigger the update for the RowStyle properly.
In real life the model class of the collections is not a string and the model class implements INotifyPropertyChange (but I don´t think this is the problem cause i just don´t update anything in the model).
I need this kind of behaviour to visible highlight more rows based on a dynamic indicator (similar to the example). If no one knows a better way I think I will implement some kind of Property in my models and update the property with a method from the ViewModel.
ViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public static MainWindowViewModel instance = null;
private string _CurrentItem;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string Property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
}
public string CurrentItem
{
get
{
return _CurrentItem;
}
set
{
if (value != _CurrentItem)
{
_CurrentItem = value;
OnPropertyChanged("CurrentItem");
OnPropertyChanged("Items");
}
}
}
public ObservableCollection<String> Items { get; set; }
public MainWindowViewModel()
{
instance = this;
Items = new ObservableCollection<string>();
CurrentItem = "First";
Items.Add("First");
Items.Add("Second");
Items.Add("First");
}
View XAML
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<local:StringToColorConverter x:Key="StringToColorConverter" />
</Window.Resources>
<DockPanel Margin="30">
<Button DockPanel.Dock="bottom" Content="From First to Second" Click="Button_Click" />
<DataGrid IsReadOnly="True" ItemsSource="{Binding Items}" ColumnWidth="*" AutoGenerateColumns="False">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background"
Value="{Binding Converter={StaticResource StringToColorConverter}}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Text" Binding="{Binding}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
Converter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var item = value as string;
if (item == MainWindowViewModel.instance?.CurrentItem)
return "Green";
return "White";
}
So sorry for the long post I hope you can comprehend my problem and of course maybe help me :)
You can involve CurrentItem using an IMultiValueConverter
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background">
<Setter.Value>
<MultiBinding Converter="{StaticResource MultiStringToColorConverter}">
<Binding />
<Binding Path="DataContext.CurrentItem"
RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type Window}}"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
The Converter
public class MultiStringToColorConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
var item = values[0] as string;
var current = values[1] as string;
if (item == current)
return new SolidColorBrush(Colors.Green);
return new SolidColorBrush(Colors.White);
}
public object[] ConvertBack(object values, Type[] targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Everything is normal !
It is by design.
Your background is bound to the n-th object of the DataGrid.
It is not bound to CurrentItem, so there is no reason the binding updates the n-th line background.
Because you have an ObservableCollection, you could put a IsSelected property in MyItem class
And you should make MyItem ràise a PropertyChanged event on IsSelected property.
Of course MyItem would implement INotifyPropertyChanged
Last, you should change the binding :
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background"
Value="{Binding Path=IsSelected,
Converter={StaticResource BooleanToColorConverter}}" />
</Style>
</DataGrid.RowStyle>
Of course changing the StringToColorConverter into BooleanToColorConvert wouldbe trivial.
Regards

Change style in ModelView (MVVM + WPF)

I have an application developed in WPF using the MVVM pattern (MVVM Light Toolkit).
So far, I had no problems, until it is time to change at runtime the style associated with some of my controls, a set of MenuItems. (They can have up to three different styles).
If I was not working with MVVM I could solve it using the command:
MenuElement_Left4.Style = (Style)FindResource("MenuButtonTabsLeft");
But because I want do it completely in MVVM, I've done these tests to achieve that:
1) Try to change the style with a binding (this has not worked):
<MenuItem x:Name="MenuElement_Left4" Header="Test" Style="{Binding SelectedStyle}">
And in the ViewModel:
public string SelectedStyle
{
get { return this.selectedStyle; }
set { this.selectedStyle = value;
RaisePropertyChanged("SelectedStyle");
}
}
private string selectedStyle;
2) Change the style with DataTrigger (this has not worked too. Raises an exception (Style Trigger to Apply another Style)):
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TestStyle}" Value="True">
<Setter Property="Style" Value="{StaticResource MenuButtonTabsLeftArrow}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=TestStyle}" Value="False">
<Setter Property="Style" Value="{StaticResource MenuButtonTabsLeft}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
At the end, I managed to solve it, using a Combox using the following code (I only use the ComboBox to change the style of the MenuItems so it is invisible). Got the idea from (How can I change an elements style at runtime?):
<MenuItem x:Name="MenuElement_Left4" Header="Test" Style="{Binding ElementName=AvailableStyles, Path=SelectedItem.Tag}">
<ComboBox Name="AvailableStyles" SelectedIndex="{Binding AvailableStylesIndex}" Visibility="Collapsed">
<ComboBoxItem Tag="{x:Null}">None</ComboBoxItem>
<ComboBoxItem Tag="{StaticResource MenuButtonTabsLeftArrow}">MenuButtonTabsLeftArrow</ComboBoxItem>
<ComboBoxItem Tag="{StaticResource MenuButtonTabsLeft}">MenuButtonTabsLeft</ComboBoxItem>
</ComboBox>
And in my ViewModel:
public int AvailableStylesIndex
{
get { return this.availableStylesIndex; }
set
{
this.availableStylesIndex = value;
RaisePropertyChanged("AvailableStylesIndex");
}
}
I'd rather use a cleaner way. Any suggestions? A piece of code would be very helpful.
Since you are keeping your styles in you resources, cleaner approch would be to use IMultiValueConverter's with you first approch something like this:
ViewModel
public string SelectedStyle
{
get { return this.selectedStyle; }
set { this.selectedStyle = value;
RaisePropertyChanged("SelectedStyle");
}
}
private string selectedStyle;
Xaml:
<MenuItem.Style>
<MultiBinding Converter="{StaticResource StyleConverter}">
<MultiBinding.Bindings>
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding Path="SelectedStyle"/>
</MultiBinding.Bindings>
</MultiBinding>
</MenuItem.Style/>
In the converter find the style you want and apply it
class StyleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
FrameworkElement targetElement = values[0] as FrameworkElement;
string styleName = values[1] as string;
if (styleName == null)
return null;
Style newStyle = (Style)targetElement.TryFindResource(styleName);
if (newStyle == null)
newStyle = (Style)targetElement.TryFindResource("MyDefaultStyleName");
return newStyle;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Taken out from Steven Robbins's answer in this post

Categories