Change Property by a Behavior disable Trigger in XAML Style - c#

Below you can se a part of my Behavior and XAML Style. So if no Behavior is attached, everything is fine. But with the attached Behavior the Trigger is not fired anymore and I only have this new Color on my background. Someone can give me a tip, how to change the Color without overriding the Trigger?
Behavior Snippet
public class ChangeColor : Behavior<FrameworkElement>
{
public string NewColor
{
get { return (string)GetValue(NewColorProperty); }
set { SetValue(NewColorProperty, value); }
}
public static DependencyProperty NewColorProperty = DependencyProperty.Register("NewColor", typeof(string), typeof(ChangeColor), new PropertyMetadata(""));
protected override void OnAttached()
{
if (!DesignerProperties.GetIsInDesignMode(this))
{
AssociatedObject.Loaded += AssociatedObject_Loaded;
}
}
public Brush DefaultColor
{
get { return (Brush)GetValue(DefaultColorProperty); }
set { SetValue(DefaultColorProperty, value); }
}
public static DependencyProperty DefaultColorProperty = DependencyProperty.Register("DefaultColor", typeof(Brush), typeof(ChangeColor), null);
private PropertyInfo _TargetProperty;
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
_TargetProperty = AssociatedObject.GetType().GetProperty("Background");
if (DefaultColor == null)
{
try
{
DefaultColor = (Brush)_TargetProperty.GetGetMethod().Invoke(AssociatedObject, null);
}
catch
{
//ignore
}
}
}
private void ChangeTheColor()
{
if ((bool)change)
{
_TargetProperty.GetSetMethod().Invoke(AssociatedObject, new object[] { NewColor });
}
else
{
_TargetProperty.GetSetMethod().Invoke(AssociatedObject, new object[] { DefaultColor });
}
}
}
XAML Snippet
<Style TargetType="Controls:CustomButton">
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsReadOnly" Value="False"/>
<Condition Property="IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Background" Value="{Extensions:ThemeColors KeyCode=BackgroundSpecialColor}"/>
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>

Try to make DependencyProperty like this.
public Brush NewColor
{
get { return (Brush)GetValue(NewColorProperty); }
set { SetValue(NewColorProperty , value); }
}
public static readonly DependencyProperty NewColorProperty =
DependencyProperty.Register("NewColor" , typeof(Brush) ,
typeof(ChangeColor) ,
new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Black) ,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
where SolidColorBrush(Colors.Black) is default color, and this set it to bind TwoWay by default FrameworkPropertyMetadataOptions.BindsTwoWayByDefault),maybe that was a problem, becouse if you dont set it there to bind TwoWay by default then you have to make that in binding Mode=TwoWay, or else your Property wont register that change. Hope that will help you.ΒΈ
EDIT: As i can see you are using Button as base class and want to change button background on trigger? I really can't say what is the best way to do it, but i would do it in generic.xaml,a and in <ControlTemplate.Triggers> i would do it like this:
<ControlTemplate.Triggers>
<Trigger Property="IsPressed"
Value="true">
<Setter Property="Background"
Value="{Binding NewColor, RelativeSource={RelativeSource TemplatedParent}}" />
</Trigger>
</ControlTemplate.Triggers>
And Up in beggining of your style you have something like this:
<Setter Property="Background"
Value="{Binding BorderBrush, RelativeSource={RelativeSource TemplatedParent}}" />
just change that line of code to this:
<Setter Property="Background"
Value="{Binding DefaultColor, RelativeSource={RelativeSource TemplatedParent}}" />
That way you have set your Default color in the begining, and your NewColor on Trigger.
and when you call that custom button in some xaml code
<Grid>
<customButton:Button DefaultColor="set your default color"
NewColor="Set your onClick color".../>

Related

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));
}
}

Saving a WPF canvas as an image following MVVM Pattern

I have a canvas, e.g. similar to this solution or many others using the ItemsControl.
Now I want a button which should be bound to an ICommand. This command should call a method of ViewModel class which can save the image.
The saving method is clear, but how do I do the binding following the MVVM pattern?
You could pass the Canvas to the ViewModel's Save method using a CommandParameter
<Button Content="Save"
Command="{Binding SaveCanvasCommand}"
CommandParameter="{Binding ElenementName=myCanvas}" ?>
<Canvas x:Name="myCanvas">
<!-- Stuff to save -->
</Canvas>
And somewhere in you ViewModel or Command you'd have
void SaveCanvasCommandExecute(object parameter)
{
UIElement toSave = (UIElement)parameter;
//.. You'd probably use RenderTargetBitmap here to save toSave.
}
If you don't want to reference UI elements in your ViewModel you could use an attached behaviour:
internal static class Behaviours
{
public static readonly DependencyProperty SaveCanvasProperty =
DependencyProperty.RegisterAttached("SaveCanvas", typeof(bool), typeof(Behaviours),
new UIPropertyMetadata(false, OnSaveCanvas));
public static void SetSaveCanvas(DependencyObject obj, bool value)
{
obj.SetValue(SaveCanvasProperty, value);
}
public static bool GetSaveCanvas(DependencyObject obj)
{
return (bool)obj.GetValue(SaveCanvasProperty);
}
private static void OnSaveCanvas(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
// Save code.....
}
}
}
Then in your ViewModel you have your Command that sets a property, also on your ViewModel:
public ICommand SaveCanvasCommand
{
get
{
if (_saveCanvasCommand == null)
_saveCanvasCommand = new RelayCommand(() => { IsSaveCanvas = true; });
return _saveCanvasCommand;
}
}
And the property which is bound to your View:
public bool IsSaveCanvas
{
get { return _isSaveCanvas; }
set
{
_isSaveCanvas = value;
RaisePropertyChanged("IsSaveCanvas");
}
}
Then hooking it all up in the Xaml looks like this:
Add a Trigger on the Control that binds the value of your ViewModel property to your attached behaviour:
<UserControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSaveCanvas}" Value="True">
<Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSaveCanvas}" Value="False">
<Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
And then bind your Button / MenuItem to the ViewModels Save Command:
<Canvas.ContextMenu>
<MenuItem Header="Save" Command="{Binding SaveCanvasCommand}"/>
</Canvas.ContextMenu>

Unable to set style-trigger property to custom dependency property

I got another problem while working with my usercontrol's xaml file -.-'
I tried to implement an IsChecked property to my custom button in order to set a different background colour if the button is checked.
So I created a DependencyProperty like this:
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(LeftMenuBtn));
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
Then I setup a new style trigger to handle this property:
<Style x:Key="ButtonEnableStates" TargetType="{x:Type Grid}">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="{DynamicResource CheckedStateGradient}" />
</Trigger>
</Style.Triggers>
</Style>
Expression Blend now underlines Property="IsChecked" and says:
The member "IsChecked" is not recognized or is not accessible.
How can I solve this problem?
Well, the Style's TargetType is Grid and the property is defined for LeftMenuBtn, not going to work like that.

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