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