I have a User Interface and I am changing the background property of the main grid. Now some give a very pleasent look but for some there is difficulty reading the text as displayed. However, a problem arises when I have approximately 20 labels around there now, and making them change and assign them color each time is making my code look ugly. I know there must be a more elegant design.
I tried to bind labels to a color but that does not work. here is code
XAML:
<Label Foreground="{Binding defColor}" Content="Settings" Height="44" HorizontalAlignment="Left" Margin="12,53,0,0" Name="label1" VerticalAlignment="Top" FontWeight="Normal" FontSize="26" />
Code behind:
SolidColorBrush defColor = new SolidColorBrush();
public SettingsWindow()
{
InitializeComponent();
defColor.Color = Colors.Black;
//defColor.Color = Colors.Black; label1.Foreground = defColor;
}
private void button4_Click(object sender, RoutedEventArgs e)
{
defColor.Color = Colors.Black;
}
Thanks
Just from looking at the C# code you posted, I think your first problem is that you've done this
SolidColorBrush defColor = new SolidColorBrush();
instead of this
public SolidColoRBrush defColor { get; set; }
You can only bind to properties.
Your constructor will now look like this
public SettingsWindow()
{
InitializeComponent();
defColor = new SolidColorBrush(Colors.Black);
this.DataContext = this; // need to tell your window where to look for binding targets
}
If you are setting the SettingsWindow DataContext = this; then the SettingsWindow class MUST implement INotifyPropertyChanged. to get the {Binding defColor} to work. Code you need:
public class SettingsWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public SettingsWindow()
{
// We are acting as our own 'ViewModel'
DataContext = this;
InitializeComponent();
}
private Color _defColor;
public Color defColor
{
get { return _defColor; }
set
{
if (_defColor != value)
{
_defColor = value;
if(null != PropertyChanged)
{
PropertyChanged(this, "defColor");
}
}
}
}
}
As for targeting all labels in your application, the correct approach is to use Style, as previously suggested.
You will have to apply this style to each Label. Omit the x:Key to make it default style for your Labels
<Style x:Key="LabelForeGroundStyle" TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="{Binding defColor}" />
</Style>
Instead of Binding each label to the same property, I think you should use style, and apply this style for each label, e.g. with your binding:
<Style x:Key="LabelForeGroundStyle" TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="{Binding defColor}" />
</Style>
or even better, with trigger:
<Style.Triggers>
<Trigger>
<Trigger Property="Background" Value="Blue">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Trigger>
</Style.Triggers>
Related
I have a user control for which I have to change color, based on mouse hover, click or none. Following MVVM. This is the code I have:
User control in XAML
<userControls:NC DataContext="{Binding NCVM}" >
</userControls:NC>
User Control View Model
public class NCVM : ObservableObject
{
public NCVM()
{
}
private NCState _currentState = NCState.InActive;
public NCState CurrentState
{
get => _currentState;
set
{
_currentState = value;
switch (_currentState)
{
case NCState.InActive:
ForegroundColor = System.Windows.Media.Brushes.LightGray;
IsActive = false;
break;
case NCState.Active:
ForegroundColor = System.Windows.Media.Brushes.White;
IsActive = true;
break;
case NCState.Hovered:
ForegroundColor = System.Windows.Media.Brushes.White;
IsActive = false;
break;
default:
ForegroundColor = System.Windows.Media.Brushes.LightGray;
IsActive = false;
break;
}
}
}
public bool _isActive;
public bool IsActive
{
get => _isActive;
set => SetProperty(ref _isActive, value);
}
private System.Windows.Media.Brush _foregroundColor = System.Windows.Media.Brushes.LightGray;
public System.Windows.Media.Brush ForegroundColor
{
get => _foregroundColor;
set => SetProperty(ref _foregroundColor, value);
}
}
Main Window View Model
public class MWVM : BVM
{
#region Private Variables
private NCVM _NCVM = new();
#endregion
public MWVM()
{
NCVM.CurrentState = NCState.Active;
}
#region Public Properties
public NCVM NCVM
{
get => _NCVM;
set => SetProperty(ref _NCVM, value);
}
#endregion
}
Right now, it's getting preset as active for checking. Now, I have to make it manual so it changes on hover, but not getting how to do with binding.
The MVVM pattern is about separating the user interface (view) from the data and application logic itself. Your example violates MVVM in that it stores the brushes and the visual states in a view model. The view model should only expose data and commands to be bound, but not user interface elements and it must not contain logic to that relates to the user interface just like managing visual states or appearance. It is too often misunderstood as creating a view model and just putting everything there.
In your case, I think that you can solve your issue by moving everything into a style. The following XAML should show your userControls:NC. There are triggers for different states like Disabled, Hover / Mouse Over. Please note that you need to set a Background, otherwise the control does not participate in hit testing and e.g. the IsMouseOver property will not be True even if you hover over it. For no background use Transparent (which is not equal to not setting a value).
<UserControl ...>
<UserControl.Style>
<Style TargetType="{x:Type userControls:NC}">
<!-- Background must be set at least to "Transparent" -->
<Setter Property="Background" Value="Black"/>
<!-- Default -->
<Setter Property="Foreground" Value="LightGray"/>
<Style.Triggers>
<!-- Hovered -->
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="White"/>
</Trigger>
<!-- Disabled -->
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="LightGray"/>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Style>
<!-- Dummy element for demonstration purposes of foreground -->
<TextBlock Text="This text shows the foreground"/>
</UserControl>
You may take a look at EventTrigger, or Triggers in general to style your control.
*Edit:
A little example, MVVM not considered, just for you to get a glimpse at triggers.
UserControl:
<UserControl x:Class="WpfApp1.UserControl1"
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:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type={x:Type local:UserControl1}}"
Height="200" Width="400">
<UserControl.Style>
<Style TargetType="UserControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsMyPropSet}" Value="True">
<Setter Property="Background" Value="Turquoise"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
<GroupBox Header="I am your usercontrol">
<Button Width="100" Height="35" Content="Toggle Property" Click="Button_Click"/>
</GroupBox>
</UserControl>
and code-behind:
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public UserControl1()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public bool IsMyPropSet { get; set; }
private void Button_Click(object sender, RoutedEventArgs e)
{
IsMyPropSet = !IsMyPropSet;
RaisePropertyChanged(nameof(IsMyPropSet));
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I have two combobox bind to the same ObservableCollection proprety, i would like to know if is possible to disable an item in combox if it's already selected in one of them ? in wpf. thx
You can bind the IsSelected property of the ComboBox item to a bool identifying a selected state in your class.
<ComboBox ItemsSource="{Binding Items}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsSelected" Value="{Binding SelectedA, Mode=OneWayToSource}"></Setter>
<Setter Property="IsEnabled" Value="{Binding SelectedB}"></Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
<ComboBox ItemsSource="{Binding Items}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsSelected" Value="{Binding SelectedB, Mode=OneWayToSource}"></Setter>
<Setter Property="IsEnabled" Value="{Binding SelectedA}"></Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Create a class which exposes a couple of bools
public class MyClass : INotifyPropertyChanged
{
private bool selectedA;
public bool SelectedA
{
get { return !selectedA; }
set { selectedA = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("SelectedA")); }
}
private bool selectedB;
public bool SelectedB
{
get { return !selectedB; }
set { selectedB = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("SelectedB")); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
(In the example I am simply reversing each selected bool in the getter, but in reality flipping the bool would probably be best performed using a converter)
You can either:
Set the IsEnabled property of the ComboBoxItem using something like this: Disallow/Block selection of disabled combobox item in wpf (Thanks Kamil for the link)
Make it look different (but still selectable);
Update the second list so it removes the selected option when the selection of the first changes; or
Apply validation after the fact (e.g. show an error icon/message, or disable the "submit" button if the two selections are the same).
Your choice will depend only on the experience you're trying to achieve.
Is it possible to base the value property of a trigger to a property of an element?
For instance, in a ControlTemplate that has a ScrollBar, I'm trying to set its Visibility property to Collapsed if its Minimum and Maximum properties are equal.
However, the following doesn't work because you can't set a Binding as the Value of a Trigger because a Trigger is not a DependencyObject.
<Trigger Property="Minimum"
SourceName="PART_ScrollBar"
Value="{Binding Maximum, SourceName=PART_ScrollBar}">
<Setter Property="Visibility"
TargetName="PART_ScrollBar"
Value="Collapsed" />
</Trigger>
So can this be done purely with triggers, or do I have to do this in code-behind?
In this scenario, Id recommend creating a custom behavior
Like this
public class MinMaxVisibilityBehavior : Behavior<ScrollBar>
{
public override void OnAttached()
{
DependencyPropertyDescriptor
.FromProperty(ScrollBar.MaximumProperty, typeof(ScrollBar))
.AddValueChanged(AssociatedObject, CheckMinMax);
DependencyPropertyDescriptor
.FromProperty(ScrollBar.MinimumProperty, typeof(ScrollBar))
.AddValueChanged(AssociatedObject, CheckMinMax);
}
private void CheckMinMax(object sender, EventArgs e)
{
AssociatedObject.Visibility = AssociatedObject.Minimum ==
AssociatedObject.Maximum ? Visibility.Hidden : Visibility.Visible;
}
}
and then in your XAML
<ScrollBar>
........
<i:Interaction.Behaviors>
<local:MinMaxVisibilityBehavior />
</i:Interaction.Behaviors>
</ScrollBar>
I have ContentPresenter with DataTemplateSelector:
...
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var model = item as ItemControlViewModel;
if (model.CurrentStatus == PrerequisitesStatus.Required)
{
return RequiredTemplate;
}
if (model.CurrentStatus == PrerequisitesStatus.Completed)
{
return FinishedTemplate;
}
...
return InProgressTemplate;
}
When CurrentStatus is changed, OnPropertyChanged is called.
I need somehow to trigger this DataTemplateSelector when the property is changed and change ContentPresenter DataTemplate. Any suggestions?
Threre are similar questions:
1
2, but I don't want to use any DataTriggers, because of too much states.
Tried to play with DataTriggers
<ContentPresenter
Grid.Column="1"
Height="16"
Width="16"
Margin="3">
<ContentPresenter.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<Setter Property="ContentPresenter.ContentTemplate" Value="{StaticResource ResourceKey=_requiredStatusTemplate}" />
</DataTrigger>
</ContentPresenter.Triggers>
</ContentPresenter>
But got an error:
Triggers collection members must be of type EventTrigger :(
As you requested an example with datatriggers in the comments, here you are:
A FrameworkElement can only have EventTriggers, therefore you get the error Message Triggers collection members must be of type EventTrigger
And also don't use a ContentPresenter directly, it is meant to be used inside a ControlTemplate. Better use a ContentControl when you want to have dynamic content.
See What's the difference between ContentControl and ContentPresenter?
And finally here's a suggestion to your DataTrigger issue. I have put it inside a style for reusability ....
XAML :
<Window x:Class="WpfApplication88.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="requiredTemplate">
<TextBlock Text="requiredTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<DataTemplate x:Key="completedTemplate">
<TextBlock Text="CompletedTemplate"></TextBlock>
<!--your stuff here-->
</DataTemplate>
<Style x:Key="selectableContentStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Required">
<Setter Property="ContentTemplate" Value="{StaticResource requiredTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="Completed">
<Setter Property="ContentTemplate" Value="{StaticResource completedTemplate}" />
</DataTrigger>
<!-- your other Status' here -->
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ContentControl Width="100" Height="100" Style="{StaticResource selectableContentStyle}"/>
</Grid>
</Window>
I could be wrong, but I believe the DataTemplateSelector is only used when the ItemContainerGenerator creates a container for an item added to the collection. Because a new container isn't generated when a property value changes, a new DataTemplate is never going to be applied via the selector.
As suggested in the comments, I would recommend you look at the VisualStateManager or data triggers, otherwise you're going to have to recreate the container for every item when one or more properties change value.
Just as an extra choice - if you want to stick to your templates, just use s binding with converter.
I came up with a behavior that would theoretically do this.
C#:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class UpdateTemplateBehavior : Behavior<ContentPresenter>
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnContentChanged));
public object Content
{
get => GetValue(ContentProperty);
set => SetValue(ContentProperty, value);
}
static void OnContentChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(object), typeof(UpdateTemplateBehavior), new FrameworkPropertyMetadata(null, OnValueChanged));
public object Value
{
get => GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is UpdateTemplateBehavior behavior)
behavior.Update();
}
public UpdateTemplateBehavior() : base() { }
protected override void OnAttached()
{
base.OnAttached();
Update();
}
void Update()
{
if (Content != null)
{
BindingOperations.ClearBinding(AssociatedObject, ContentPresenter.ContentProperty);
AssociatedObject.Content = null;
BindingOperations.SetBinding(AssociatedObject, ContentPresenter.ContentProperty, new Binding() { Path = nameof(Content), Source = this });
}
}
}
XAML:
<ContentPresenter ContentTemplateSelector="{StaticResource MySelector}">
<i:Interaction.Behaviors>
<Behavior:UpdateTemplateBehavior Content="{Binding SomeContent}"
Value="{Binding SomeValue}"/>
</i:Interaction.Behaviors>
</ContentPresenter>
The content is "updated" (by clearing and then resetting the binding) when the content (in this example, "SomeContent") and an arbitrary value (in this example, "SomeValue") is changed, as well as when the behavior is first attached.
An update is not made unless the content is not null (my project-specific requirement). Not updating upon attaching may avoid unintentionally updating twice at once, but if the value is initially null, an update wouldn't occur until the value changes at least once.
Note: In the above example, I am not sure if the behavior has the same data context as the ContentPresenter. I use a helper class that I did not include here for brevity. Keep that in mind when testing...
I am having an issue when using a DataTrigger to manipulate the IsEnabled property of a control. Normally it works fine, however when I initialize the IsEnabled state within the View's Initialized event, the dynamic stylizing no longer works.
Here's my code. I trimmed it down to the simplest example I could.
Why is this occurring, and what can I do to allow me to set IsEnabled both by a style trigger and by initializing it in the code behind?
Thanks in advance!
View:
(Contains a textbox that should be enabled/disabled depending on the value of a checkbox)
<Window x:Class="IsEnabled.Views.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Initialized="Window_Initialized">
<StackPanel Orientation="Vertical">
<TextBox x:Name="txtTarget" Width="200">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ToggleValue}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<CheckBox x:Name="chkSource" IsChecked="{Binding Path=ToggleValue}" />
</StackPanel>
</Window>
View Codebehind:
(The only addition is the implementation of the Initialized event setting the inital state for IsEnabled)
using System;
using System.Windows;
namespace IsEnabled.Views
{
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
}
private void Window_Initialized(object sender, EventArgs e)
{
txtTarget.IsEnabled = false;
}
}
}
ViewModel:
(ViewModelBase holds the implementation of the INotifyPropertyChanged interface)
using System;
namespace IsEnabled.ViewModels
{
class MainViewModel : ViewModelBase
{
private bool _ToggleValue;
public bool ToggleValue
{
get { return _ToggleValue; }
set
{
_ToggleValue = value;
OnPropertyChanged(this, "ToggleValue");
}
}
}
}
Have a look at dependency property value precedence, and how changing values from different places, Styles, Triggers, Animations etc. work together.
Add to your Binding Mode=TwoWay and it should work.