[My main idea of this is to set visible/hidden for a usercontrol. I used WPF with Mvvmcross.]
I have a user control call SpinningWheelUserControl. I want to visible/hide it with the datatrigger. Below is my xaml code in App.xaml
In App.xaml I have added the namespace of the usercontrol as below.
xmlns:local="clr-namespace:UserControl"
The following is a style setting for my usercontrol.
<Style x:Key="SpinningWheel" TargetType="{x:Type local:SpinningWheelUserControl}" >
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="true">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding IsVisible}" Value="false">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
There is a class for SpinningWheel
public class SpinningWheelViewModel
: MvxNotifyPropertyChanged
{
public bool IsVisible { get; set; }
}
In a constructor of parent class, i use like this code
SpinningWheel = new SpinningWheelViewModel();
SpinningWheel.IsVisible = false;
The usercontrol is hidden for a first running. But when I change the IsVisble to true, it has no change.
SpinningWheel.IsVisible = true
You need to set Visibility instead of IsVisible like this:
SpinningWheel.Visibility = Visibility.Visible;
Oh now i see, you are setting your custom IsVisibility instead of UIElement property.
Issue with your code is you haven't raised PropertyChanged to let UI know that some property change in underlying source object.
private bool isVisible;
public bool IsVisible
{
get { return isVisible;}
set
{
if(isVisible != value)
{
isVisible = value;
RaisePropertyChanged("IsVisible");
}
}
}
Assuming you have implemented INotifyPropertyChanged on your class.
This n+1 video called N=34 : a data-bound busy dialog shows exactly how to do what you are trying to do.
Related
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
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 some elements on my main filter, in my wpf application.
But I donĀ“t want to set visibility and isEnabled one by one in get/set. Is there more elegant way, how to change it from view model?
Thank you! :)
You can use a DataTrigger to change some properties of your Button based on the view model properties:
<Window.Resources>
<local:MyViewModel x:Key="viewModelInstance"></local:MyViewModel>
</Window.Resources>
<StackPanel>
<Button DataContext="{StaticResource viewModelInstance}" Content="My Button">
<Button.Style>
<Style TargetType="Button">
<!-- Default style is Visible and Enabled -->
<Setter Property="IsEnabled" Value="True"></Setter>
<Setter Property="Visibility" Value="Visible"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsAllowed}" Value="False">
<!-- Hide and disable when IsAllowed is false -->
<Setter Property="IsEnabled" Value="False"></Setter>
<Setter Property="Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
Assuming you have a view model class defined like:
public class MyViewModel : INotifyPropertyChanged {
public bool IsAllowed { get; set; } = true;
//Put more logic here of course.
}
MyViewModel should implement INotifyPropertyChanged to automatically notify the UI to update the view when the IsAllowed property changes, for example like this:
public class MyViewModel : INotifyPropertyChanged {
//Backing field for IsAllowed
private bool _isAllowed = true;
/// <summary>
/// Gets or sets the IsAllowed property.
/// </summary>
public bool IsAllowed {
get => _isAllowed; set {
if (_isAllowed != value) {
_isAllowed = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsAllowed)));
}
}
}
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
}
You can also look into the IValueCOnverter interface and XAML converters for other ways.
I use a DataGrid in WPF to display values.
Now I want to have green and red rows. I tried it out with DataTrigger but nothing happends.
My XAML:
<DataGrid x:Name="dgAbos" ItemsSource="{Binding Source=AboList}" HorizontalAlignment="Stretch" Margin="10,30,10,10" VerticalAlignment="Stretch" Height="Auto" Width="Auto">
<DataGrid.Columns>
<DataGridTextColumn Header="ItemID" Binding="{Binding ItemID}" />
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Active}" Value="false">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Active}" Value="true">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
The Binding type is:
ObservableCollection<OPCItem> AboList = new ObservableCollection<OPCItem>();
And the Item to display is OPCItem:
class OPCItem
{
public String ItemID { get; set; }
public String Name { get; set; }
public String Value { get; set; }
public DateTime DateTime { get; set; }
public String Group { get; set; }
private Boolean _Active;
public String Active
{
get
{
return (_Active == true ? "Aktiv" : "Inaktiv");
}
set
{
_Active = Convert.ToBoolean(value);
}
}
}
How I fill the list:
AboList.Add(new OPCItem { ItemID = Item.ItemID, Group = GroupName, Active = "true" });
But the row doesnt change the color, why?
The value of your Active property is never "true" nor "false", so the triggers are never actually triggered. You should modify the expected values of your triggers to reflect the values that Active might take (which are "Aktiv" and "Inaktiv"):
<DataTrigger Binding="{Binding Active}" Value="Inaktiv">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Active}" Value="Aktiv">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
This is a really bad idea:
private Boolean _Active;
public String Active
{
get
{
return (_Active == true ? "Aktiv" : "Inaktiv");
}
set
{
_Active = Convert.ToBoolean(value);
}
}
So you set Active to "true" or "false", and you get back "Aktiv" or "Inaktiv"? The natural result of that is you confuse yourself to the point where you write a trigger assuming that Active.get will return "true" or "false" instead of "Activ" or "Inaktiv". You can fix the immediate problem by just fixing the DataTrigger.Value attributes as Grx70 suggests, but it's better in the long run to fix the underlying issue and to avoid bad habits like this. Particularly if you intend to work in the field, you really don't want to be writing this kind of stuff. Active looks like a simple property, but the behavior is nothing that anybody would ever expect, so anybody interacting with it will be taken by surprise, and then they'll have to read the code to figure out what it does. Think about it this way: When you see a property on a WPF control called IsEnabled, just by looking at the property name you know exactly what it means, how to use it, and what type it is. It makes your life easier.
So instead, I recommend you make your boolean property an actual boolean:
private bool _isActive;
public bool IsActive {
get { return _isActive; }
set { _isActive = value; }
}
XAML:
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="false">
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsActive}" Value="true">
<Setter Property="Background" Value="Green"></Setter>
</DataTrigger>
</Style.Triggers>
If you wan to display the strings "Aktiv" and "Inaktiv" somewhere in the UI, you can do that with another trigger, or with a readonly property called something like ActiveDisplayString.
You should probably be implementing INotifyPropertyChanged as well in that OPCItem class; that way, you can change the property values after they're in the grid, and the UI will reflect the changes.
I'm getting SO frustrated here.. I can't get these datatriggers to consistently work..
It works when I first run the program as I initialize a global UdpMessageAuthentication class (as it sets it to "test0"... but then I have a button that calls the SendAuthPacket method.. and from debugging I see it go into the OnPropertyChanged when I hit the button but the label won't change caption or color or any other property...once I use AuthenticateStatus to "test1".
Obviously I tried more realistic variables besides test0 and test1 but no matter what I'm doing I can't get the triggers to update
Please help =T
<Label Name="Label_Authentication" Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="0" FontSize="14">
<Label.Style>
<Style TargetType="Label">
<Setter Property="Content" Value="Initial Content"></Setter>
<Setter Property="Foreground" Value ="Red"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=AuthenticateStatus}" Value="test0">
<Setter Property="Content" Value="Authentication Required" />
<Setter Property="Foreground" Value="Red"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=AuthenticateStatus}" Value="test1">
<Setter Property="Content" Value="Attempting Authentication..." />
<Setter Property="Background" Value="Blue"></Setter>
</DataTrigger>
etc....
public class UdpMessageAuthentication : INotifyPropertyChanged
{
private string _authenticateStatus;
public string AuthenticateStatus
{
get { return _authenticateStatus; }
set
{
if (_authenticateStatus != value)
{
_authenticateStatus = value;
OnPropertyChanged("Authenticate Status");
}
}
}
public UdpMessageAuthentication()
{
_udpClient = new UdpClient();
AuthenticateStatus = "test0";
}
public void SendAuthPacket(IPAddress ip, string userID)
{
etc etc....
AuthenticateStatus = "test1";
etc etc....
}
Make sure you explicitly set the Mode Property when you use (any) binding.
<DataTrigger Binding="{Binding Path=AuthenticateStatus, Mode=OneWay}" Value="test0">
Also, you have to make sure you're raising the property changed event with the PropertyName string set to exactly the name of the proprty being raised, since the system is using Reflection under the hood to find the changed property based on said string. Thus, try using this in your ViewModel:
get { return _authenticateStatus; }
set
{
if (_authenticateStatus != value)
{
_authenticateStatus = value;
OnPropertyChanged("AuthenticateStatus");
}
}