How to set style property of an element in WPF - c#

If IsEnabled property is true, I need to set the Style attribute otherwise it shouldn't be set. In the examples I've seen so far, style properties are set but not the style attribute itself. Below code is not working using Triggers.
<TabItem.Style>
<Style TargetType="TabItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
<Setter Property="Style" Value="DotcomTabItemStyle" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>

Since you're setting the Trigger through Style, changing the Style would also remove the Trigger... Not really sure if that'll work out :P
Anyway, you're making a mistake on your Setter (setting the resource name directly, not through a static or dynamic resource reference). And you don't need a DataTrigger. It should be:
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Style" Value="{StaticResource DotcomTabItemStyle}" />
</Trigger>
But as I said, this won't probably work as intended, since you're trying to modify the Style property from within the current Style...
One way or another, you'll end up adding different Setters for each property, probably either modifying the DotcomTabItemStyle Style you already have, or creating a new one (based on that one, maybe).
EDIT - Or you could use a Converter and bind the Style property to the IsEnabled property.
I've created a reusable Converter for all this kind of situations:
public class ConditionalSetterConverter : IValueConverter
{
public bool Inverse { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool flag = (bool)value;
if (flag ^ Inverse)
return parameter;
else
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You use it like this:
<Window>
<Window.Resources>
<converters:ConditionalSetterConverter x:Key="InverseConditionalSetterConverter"
Inverse="True" />
<Style x:Key="DotcomTabItemStyle" TargetType="TabItem">...</Style>
</Window.Resources>
<TabControl>
<TabItem Style="{Binding IsEnabled,
RelativeSource={RelativeSource Mode=Self},
Converter={StaticResource InverseConditionalSetterConverter},
ConverterParameter={StaticResource DotcomTabItemStyle}}" />
</TabControl>
</Window>
EDIT 2 - OR... You could use a Style selector. ItemsControls like TabControl have a property called ItemContainerStyleSelector, of type StyleSelector.
You'd have to create your own class, inheriting StyleSelector, and override the SelectStyle function to include your custom logic there.
Something like this:
public class DotcomTabItemStyleEnabledSelector : StyleSelector
{
private Style style = null;
public override System.Windows.Style SelectStyle(object item, System.Windows.DependencyObject container)
{
var tabItem = container as TabItem;
if (tabItem != null && tabItem.IsEnabled)
{
if (style == null)
style = textBox.TryFindResource("DotcomTabItemStyle") as Style;
return style;
}
return null;
}
}
I've never used Style selectors, so I'm not really sure if this would work out of the box, but at least you get the idea.

Related

How to bind two Enum property

I'm trying to bind two properties so I can change the background color in DataGrid based on their value. Based on these answers
How to bind an enum to a combobox control in WPF?
best way to bind enum propery in datagrid
I have implemented the advice in my code, but I'm missing something and it doesn't work.
Thanks for any advices.
namespace Example
{
public class ExampleClass
{
private ExampleObject exampleObject;
public ExampleObject ExampleObject { get; set; }
}
}
namespace Object
{
public class ExampleObject
{
private Value value;
public ExampleObject ExampleObject { get; set; }
}
public enum Value
{
High,
Low
}
}
Wpf DataGrid DataTrigger where I am changing the colour
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding ExampleObject.Value}" Value="{StaticResource CellConverter}">
<Setter Property="Background" Value="Green">
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ExampleObject.Value}" Value="{StaticResource CellConverter}">
<Setter Property="Background" Value="Red">
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
CellConvertor class
public class CellConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Value input = ((Value)value);
switch (input)
{
case Value.High:
return "High";
case Value.Low:
return "Low";
default:
return DependencyProperty.UnsetValue;
}
}
}
You must fix the trigger condition in your example.
Additionally, in order to bind to the ExampleObject.Value enum value, the ExampleObject.Value must be a public property:
public class ExampleObject
{
public Value Value {get; set;}
}
In XAML you reference enum values like static variables and constants by using the x:Static markup extension. In fact, C# enum is implemented as a set of constants. When using the x:Static extension, your current value converter CellConverter becomes obsolete:
<!--
In this example the namespace that defines the enum type 'Value'
is assumed to be registered under the XAML alias 'local'
-->
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding ExampleObject.Value}"
Value="{x:Static local:Value.Low}">
<Setter Property="Background" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding ExampleObject.Value}"
Value="{x:Static local:Value.High}">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
Remarks: in general, if you want to use a value converter, then you must configure the Binding accordingly and assign the IValueConverter instance to the Binding.Converter property. You can't assign a IValueConverter directly to a property to make it convert. The IValueConverter must receive an input that he can convert to produce the output. The input is the value provided by the actual Binding (and that's why Binding has a Binding.Converter property - they always go in tandem).
See Microsoft Docs: DataBinding Overview (Data conversion)
Note, since the property DataTrigger.Value is not a DependencyProperty, you can't define its value via a Binding.
Converter example:
<Window>
<Window.Resources>
<MyValueConverter x:Name="MyValueConverter" />
</Window.Resources>
<SomeObject SomeDependencyProperty="{Binding SourceProperty, Converter={StaticResource MyValueConverter}}" />
</Window>
It's also recommende best practice to define a default enum value to avoid errors. The default value for an enum instance is always 0 which in your case would default to High. Better allow to identify an unset state by adding an explicit 0 value named None or Default:
public enum Value
{
None = 0,
High,
Low
}
See: Microsoft Docs: Enum Design

Best way to build omplex, conditional binding

Consider I have to style basing on two properties:
<Label Style={Binding IsEnabled, Convert={x:Static IsEnabledToStyleConverter}} />
or
<Label Style={Binding IsRequired, Convert={x:Static IsEnabledToStyleConverter}} />
TO determine wheter use binding with IsEnabled or IsRequired is other property - UseRequried. How Can is choose between those two bindings basin on UseRequired?
I have tried to approaches:
Another value converter
I have create own value converter:
public class ControlToLableStleConverter: IValueConverter
{
public object Convert(object value..)
{
var myCtrl = (MyControl) value;
if (myCtrl.UseRequired)
//return style based on IsRequired property
else
//return style based on IsEnabled property
}
}
But the problem is that style is no changing at IsEnabled or IsRequired changed. Quite obvious, so this solution is out.
DataTrigger
I have also created DataTriggers:
<Label>
<Label.Triggers>
<DataTrigger Binding="{Binding UseRequired}" Value="True">
<Setters>
<Setter Property="Style" Value="{Binding IsRequired ....">
</Setters>
</DataTrigger>
<DataTrigger Binding="{Binding UseRequired}" Value="False">
<Setters>
<Setter Property="Style" Value="{Binding IsEnabled ....">
</Setters>
</DataTrigger>
</Label.Triggers>
</Label>
But Label.Triggers can contain only EventTrigger elements.. What can i do else?
You can use MultiBinding in this case, though it might be quite verbose:
<Label>
<Label.Style>
<MultiBinding Converter="{StaticResource yourConverter}">
<Binding Path="IsEnabled" />
<Binding Path="IsRequired" />
</MultiBinding>
</Label.Style>
</Label>
And converter is then:
public class StyleConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
// take some caution here, because values can be null or DependencyProperty.UnsetValue in certain cases
var enabled = (bool) values[0];
var required = (bool) values[1];
// choose style
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Set the triggers within the style.
<Style TargetType="{x:Type MyControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding UseRequired}" Value="True">
<Setters>
<!-- Set properties here, not the Style -->
</Setters>
</DataTrigger>
<DataTrigger Binding="{Binding UseRequired}" Value="False">
<Setters>
<!-- Set properties here, not the Style -->
</Setters>
</DataTrigger>
</Style.Triggers>
</Style>
If you can't change properties then your best option is to use a StyleSelector (MSDN).
Put the such logic in the code behind and trigger changes off of UseRequiered (or elsewhere) and trigger all property changes such as
public bool UseRequiered
{
get { return _UseRequried; }
set { _UseRequried = value; DetermineStyle(); }// PropertyChanged in DetermineStyle()
}
public bool IsRequiered
{
get { return _IsRequiered; }
set { _IsRequiered = value; DetermineStyle();} // PropertyChanged in DetermineStyle()
}
public bool IsEnabled
{
get { return _IsEnabled; }
set { _IsEnabled = value; DetermineStyle(); } // Prop change in DetermineStyle
}
private void DetermineStyle()
{
_UseRequiered = { Whatever logic is deemed };
_IsRequired = { Whatever logic is deemed };
_IsEnabled = { Whatever logic is deemed };
OnPropertyChanged("IsRequired");
OnPropertyChanged("IsEnabled");
OnPropertyChanged("UseRequiered");
}

Datagrid attach rowloading event to all datagrids in project

I want to attach a particular RowLoading event to all DataGrid in all datagrids in the project (there are around 20 of them). The event is this:
private void dataGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
e.Row.Header = (e.Row.GetIndex() + 1).ToString();
}
I want to attach the event in startup of the project like this:
EventManager.RegisterClassHandler(typeof(DataGrid), DataGrid.LoadingRowEvent, new RoutedEventHandler(dataGrid_LoadingRow));
Unfortunately the DataGrid.LoadingRowEvent gives an error since there is no event with DataGrid class with that name present. However there is an event with that name and I can manually add the event for each grid.
Is there any way to do this without creating a custom control or changing the DataGrid declaration everywhere it has been used?
The LoadingRow event is not implemented as a routed event in WPF so you can't use the trick with EventManager.
You don't need a custom control. Just derive the DataGrid class:
class MyDataGrid : DataGrid
{
protected override void OnLoadingRow(DataGridRowEventArgs e)
{
base.OnLoadingRow(e);
}
}
So in using the MyDataGrid instead of DataGrid class you have full control over what happens in OnLoadingRow.
Just in case your question is only about the row index in the header, you wouldn't need to handle the LoadingRow event. Instead, you may use a binding to the AlternationIndex property:
<DataGrid AlternationCount="2147483647" ...>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Header"
Value="{Binding Path=(ItemsControl.AlternationIndex),
RelativeSource={RelativeSource Self},
Converter={StaticResource RowIndexConverter}}"/>
</Style>
</DataGrid.RowStyle>
</DataGrid>
You may put this in a default DataGrid style, so that it automatically applies to all DataGrid instances:
<Style TargetType="DataGrid">
<Setter Property="AlternationCount" Value="2147483647"/>
<Setter Property="RowStyle">
<Setter.Value>
<Style TargetType="DataGridRow">
<Setter Property="Header"
Value="{Binding Path=(ItemsControl.AlternationIndex),
RelativeSource={RelativeSource Self},
Converter={StaticResource RowIndexConverter}}"/>
</Style>
</Setter.Value>
</Setter>
</Style>
The binding converter would look like this:
public class RowIndexConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
return string.Format("{0}", (int)value + 1);
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}

Bind Drawing.Color from settings with Style in XAML

How to bind Color Bkg (System.Drawing.Color), defined in settings, with Style in XAML?
xmlns:props="clr-namespace:App.Properties"
<Style TargetType="{x:Type StackPanel}" x:Key="_itemStyle">
<Setter Property="Background" Value="{Binding Path=Bkg, Source={x:Static props:Settings.Default}}"/>
Background property is of type System.Windows.Media.Color, so it needs to be somehow converted?
Panel.Background property is of a System.Windows.Media.Brush type and not System.Windows.Media.Color therefore you need to convert it into SolidColorBrush. Below you can find both case scenarios:
Setting is of System.Windows.Media.Color type
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="{Binding Source={x:Static props:Settings.Default}, Path=Bkg}"/>
</Setter.Value>
</Setter>
Setting is of System.Drawing.Color type: for this you need custom IValueConverter to convert it into SolidColorBrush:
public class ColorToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var dc = (System.Drawing.Color)value;
return new SolidColorBrush(new Color { A = dc.A, R = dc.R, G = dc.G, B = dc.B });
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
which you define in your resources:
<Window.Resources>
<local:ColorToBrushConverter x:Key="ColorToBrushConverter"/>
</Window.Resources>
and you can use it like this:
<Setter Property="Background" Value="{Binding Source={x:Static props:Settings.Default}, Path=Bkg, Converter={StaticResource ColorToBrushConverter}}"/>
As you would know that background property is of solidbrush type so its value can be set or get only with some solidbrush typw property . so what you can do is make a solidbrush type property in place of color like this..in your setting class.
and now every thing just work fine..
static SolidColorBrush brush = new SolidColorBrush(Colors.Red);
public static SolidColorBrush colorBrush
{
get
{
return brush;
}
}
if you dont want to do that then you have to use value converter ..for that you can follow
this link..hope it helps you..
Simply create a setting of type System.Windows.Media.SolidColorBrush.
Select Browse... from the Type ComboBox of the new setting, then select PresentationCore -> System.Windows.Media -> SolidColorBrush.
You may now directly use that setting as you already did:
<Setter Property="Background"
Value="{Binding Path=Bkg, Source={x:Static props:Settings.Default}}"/>

using storyboard to raise a custom command. Error: Storyboard Not freezable

I have having issues with a storyboard that is complaining about being unfreezable. There are alot of links on google about this, however I am not sure from reading that information how I can achieve what I want. (ie pretty much just execute a custom command from IsMouseOver property change). I am using data templating to change my listview to look like the link I have provided in the information below:
My resource dictionary:
<DataSourceProviders:ServiceLocatorProvider ServiceType="{x:Type Interfaces:IGestureBuilderModuleController}" x:Key="GestureController"/>
<Converters:IsGestureBeingBuiltConverter x:Key="IsGestureBeingBuildConverter" />
My UI looks like:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver}" Value="True" />
<Condition Binding="{Binding Path=CurrentGestureState, Converter={StaticResource IsGestureBeingBuildConverter}}" Value="True" />
</MultiDataTrigger.Conditions>
<!--TODO:Need to add a button to this control. Then use the buttons event to trigger command.-->
<MultiDataTrigger.ExitActions>
<BeginStoryboard>
<StoryBoard:CommandTimeline StoryBoard:CommandStoryboard.Command="{Binding Source={StaticResource ResourceKey=GestureController}, Path=AddToGestureCommand}" />
</BeginStoryboard>
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
My converter looks like:
[ValueConversion(typeof(GestureState), typeof(bool))]
public class IsGestureBeingBuiltConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Equals(value, GestureState.BeingConstructed);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
My GestureState Enum looks like:
public enum GestureState
{
Finished,
BeingConstructed
}
My Gesture controller/command looks like:
public class GestureBuilderModuleController : ModuleController, IGestureBuilderModuleController
{
public ICommand AddToGestureCommand
{
get { return new DelegateCommand<GestureBuilderViewModel>(viewModel => viewModel.AddToGesture = true); }
}
}
My viewmodel looks like:
public GestureableItemViewModel ItemBeingAdded { get; set; }
public virtual bool AddToGesture
{
get { return false; }
set
{
if (ItemBeingAdded == null) return;
if(CurrentGestureState != GestureState.BeingConstructed) return;
SelectedItems.Add(ItemBeingAdded);
}
}
The exception I am getting is:
InvalidOperation: Cannot freeze storyboard.
My UI looks LIKE this:
http://socialbarrel.com/wp-content/uploads/2011/03/Android-Like-Gesture-Unlock-Screen-Being-Tested-By-Apple-Report.jpg?98c14d
Current understanding:
I understand from reading that storyboards need to be freezable for quick access across threads where they are unfrozen.
My Question is how can I make my binding in a way that it is freezable OR achieve what I want using an alternate approach. My core problem is that I want to raise a custom command or event when mousing over a listitem when capturing a gesture.
You don't need Storyboard. In your Multidatatrigger, use a OneWayToTarget DataBinding to set a property in your ViewModel. That property's setter will be invoked when the trigger condition meets. In that setter you can call "this.AddToGesture = true".

Categories

Resources