I want to compare two dynamic values User_id and user_id for equality and setting one property Cursor. Also, when the cursor is hand, I have to execute one function. How to do it? This is the code that I am using:
<DataTrigger Binding="{Binding Path=User_id}" Value="{Binding Path=user_id}">
<Setter Property="Cursor" Value="Hand"/>
</DataTrigger>
There are a couple options to attack this.
#1. Multibinding Converter
You can use Multibindingto input the two values into a IMultiValueConverter. To use this type of binding in your DataTrigger, you would use follow the following syntax.
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding>
<MultiBinding.Converter>
<local:EqualityConverter />
</MultiBinding.Converter>
<Binding Path="User_id" />
<Binding Path="user_id" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Window.Cursor" Value="Hand"/>
</DataTrigger>
The MultiBinding.Converteris set to a new instance of EqualityConverter, which is a class I created that implements the IMultiValueConverter interface. This class will do the comparison for you. The DataTrigger triggers when this converter returns true.
public class EqualityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length < 2)
return false;
return values[0].Equals(values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
#2. MVVM Pattern
I'm not sure where your DataContext is coming from, but if possible, you may want to consider using a view model for your binding. The view model could expose a property that does the equality comparison for you. Something like this.
public class UserViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _User_id;
private int _user_id;
public int User_id
{
get
{
return _User_id;
}
set
{
if (_User_id != value)
{
_User_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("User_id"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsUserIdsEqual"));
DoSomething();
}
}
}
public int user_id
{
get
{
return _user_id;
}
set
{
if (_user_id != value)
{
_user_id = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("user_id"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsUserIdsEqual"));
DoSomething();
}
}
}
public bool IsUserIdsEqual
{
get { return _user_id == _User_id; }
}
private void DoSomething()
{
if (this.IsUserIdsEqual)
{
//Do something when they are equal.
}
}
}
If using a view model like this, your DataTrigger could simplify to..
<DataTrigger Binding="{Binding Path=IsUserIdsEqual}" Value="True">
<Setter Property="Window.Cursor" Value="Hand"/>
</DataTrigger>
Regarding executing a function on the trigger, I added a DoSomething method to highlight how the view model could be used to execute a function when the two IDs are equal. I'm not sure if that would work for your case because I'm not sure what the intent of the function call is, but it is a way to execute a function when a condition changes.
Related
I am trying to highlight dates with the color "LigthPink" for important dates of the appointments that are scheduled. In my project in WPF MVVM, I created a code, but I cannot update the dates.
I arrived at the following code:
class ConverterHigligthdate: IValueConverter
{
static BindableCollection<DateTime> dict = new BindableCollection<DateTime>();
public event PropertyChangedEventHandler PropertyChanged;
static ConverterHigligthdate()
{
dict.Add(DateTime.Today);
dict.Add(DateTime.Today.AddDays(2));
dict.Add(DateTime.Today.AddDays(-10));
dict.Add(DateTime.Today.AddDays(-20));
dict.Add(DateTime.Today.AddDays(-15));
}
public static void AddDate(DateTime date)
{
dict.Add(date);
}
public static void RemoveDate(DateTime date)
{
dict.Remove(date);
}
public void Clear()
{
dict.Clear();
dict.Refresh();
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = null;
if (dict.Contains((DateTime)value))
text = null;
else
text = "";
return text;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in the view:
<Window.Resources>
<local:ConverterHigligthdate x:Key="ConverterHigligthdate"/>
<Style x:Key="calendarDayButtonStyle" TargetType="{x:Type CalendarDayButton}">
<Setter Property="Margin" Value="8"/>
<Setter Property="FontSize" Value="13"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource ConverterHigligthdate}}" Value="{x:Null}">
<Setter Property="Background" Value="LightPink"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid Margin="5">
<Calendar SelectionMode="MultipleRange" CalendarDayButtonStyle="{DynamicResource calendarDayButtonStyle}"/>
</Grid>
result
Does anyone know how to implement something that makes this work?
You're going about this the wrong way. With MVVM you always do business logic in your view model layer, never in your converters (they're part of the view layer).
There are numerous ways of going about this, but generally you want your view model layer to prepare your data in a format that the view can readily consume. For the purpose of performance, let's wrap all your selected dates in a lookup table:
public class MainViewModel
{
public HashSet<DateTime> Dates { get; } = new HashSet<DateTime>();
public MainViewModel()
{
// highlight today and tomorrow
this.Dates.Add(DateTime.Today);
this.Dates.Add(DateTime.Today.AddDays(1));
}
}
Now in your CalendarDayButtonStyle you want to add a DataTrigger. When the date for the button in question is in your collection, that's when you want to change the background color:
<Style x:Key="CalendarDayButtonStyle" TargetType="CalendarDayButton">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource LookupConverter}">
<Binding />
<Binding Path="DataContext.Dates" RelativeSource="{RelativeSource AncestorType=Calendar}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Background" Value="Pink" />
</DataTrigger>
</Style.Triggers>
</Style>
So all you need now is a converter to do the lookup. We need to pass in both the lookup table as well as the value to look up, so we can use a MultiBinding. This is in fact logic that could have been placed in the view model if we really wanted to, but it doesn't reference any view-model specific data, and it can be re-used elsewhere, so we'll bend the rules a tiny bit:
public class LookupConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var date = (DateTime)values[0];
var dates = values[1] as HashSet<DateTime>;
return dates.Contains(date);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And that's all you need. Result:
I have a user control with a TextBox that needs to be validated. The validation will vary according to the value of a dependency property in the UC, so I need to pass that as a parameter. To pass a parameter I'm using Passing a data-bound value to a validation rule as a guide. However, the binding I'm using doesn't work and I don't know why. I've beat my head against it, googled everything I can think of, no joy.
Here's the code. Hopefully I've provided enough ...
In the user control I have this XAML.
<TextBox Name="ValueBox"
PreviewMouseLeftButtonUp="OnPreviewMouseLeftButtonUp"
Height="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=Height}"
BorderThickness="0"
TextAlignment="Center"
VerticalContentAlignment="Center">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=IsControlEnabled}"
Value="False">
<Setter Property="Background" Value="{StaticResource DisabledColor}"/>
</DataTrigger>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=InteractionMode}"
Value="{x:Static local:TreatmentScheduleNumberBoxUserControl+InteractionModes.Select}">
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="Cursor" Value="{x:Static Cursors.Hand}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
<TextBox.Resources>
<local:NumberBoxValueConverter x:Key="NumberBoxConverter"/>
</TextBox.Resources>
<TextBox.Text>
<tools:ConverterBindableParameter
Converter="{StaticResource NumberBoxConverter}"
ConverterParameterBinding="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TreatmentLampType}">
<!--https://social.technet.microsoft.com/wiki/contents/articles/31422.wpf-passing-a-data-bound-value-to-a-validation-rule.aspx-->
<tools:ConverterBindableParameter.Binding>
<Binding RelativeSource="{RelativeSource AncestorType=UserControl}" Path="Value" FallbackValue="3">
<Binding.ValidationRules>
<local:NumberBoxValidationRule>
<local:NumberBoxValidationRule.Wrapper>
<local:Wrapper NumberBoxUsage1="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=NumberBoxUsage
, Converter={StaticResource DebugDummyConverter, PresentationTraceSources.TraceLevel=High}}" />
</local:NumberBoxValidationRule.Wrapper>
</local:NumberBoxValidationRule>
</Binding.ValidationRules>
</Binding>
</tools:ConverterBindableParameter.Binding>
</tools:ConverterBindableParameter>
</TextBox.Text>
</TextBox>
The problem lies in this binding, where NumberBoxUsage1 is a dependency property in validation environment and NumberBoxUsage is a dependency property in the UC.
<local:Wrapper NumberBoxUsage1="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=NumberBoxUsage
, Converter={StaticResource DebugDummyConverter, PresentationTraceSources.TraceLevel=High}}" />
When it runs, NumberBoxUsage1 remains the default and isn't assigned the value of NumberBoxUsage. I can change the binding to a literal assignment and that works. I've added a dummy converter, as shown, as well as PresentationTraceSourcesbut the converter is never called and there is no trace in the Output window. Any help appreciated.
I might add that everything else in this TextBox works fine. Here's the relevant C# stuff.
Wrapper
public class Wrapper : DependencyObject
{
public NumberBoxUsages NumberBoxUsage1 {
get => (NumberBoxUsages)GetValue(NumberBoxUsage1Property);
set => SetValue(NumberBoxUsage1Property, value);
}
public static readonly DependencyProperty NumberBoxUsage1Property =
DependencyProperty.Register(nameof(NumberBoxUsage1), typeof(NumberBoxUsages), typeof(Wrapper),
new FrameworkPropertyMetadata(
NumberBoxUsages.UvPrim,
(sender, e) =>
{
var dObj = sender as Wrapper;
var x = dObj.NumberBoxUsage1;
// leave for debugging help
}
));
}
NumberBoxValidationRule
public class NumberBoxValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value == null)
{
return new ValidationResult(false, "Please enter a value");
}
if (Wrapper.NumberBoxUsage1 == NumberBoxUsages.UvbPriPct)
{
}
return ValidationResult.ValidResult;
}
public Wrapper Wrapper { get; set; }
}
ConverterBindableParameter
public class ConverterBindableParameter : MarkupExtension
{
#region Public Properties
public Binding Binding { get; set; }
public IValueConverter Converter { get; set; }
public Binding ConverterParameterBinding { get; set; }
#endregion
#region Overridden Methods
public override object ProvideValue(IServiceProvider serviceProvider)
{
var multiBinding = new MultiBinding();
multiBinding.Bindings.Add(Binding);
multiBinding.Bindings.Add(ConverterParameterBinding);
var adapter = new MultiValueConverterAdapter
{
Converter = Converter
};
multiBinding.Converter = adapter;
return multiBinding.ProvideValue(serviceProvider);
}
[ContentProperty("Converter")]
public class MultiValueConverterAdapter : IMultiValueConverter
{
public IValueConverter Converter { get; set; }
private object lastParameter;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (Converter == null) return values[0]; // Required for VS design-time
if (values.Length > 1) lastParameter = values[1];
return Converter.Convert(values[0], targetType, lastParameter, culture);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
if (Converter == null) return new object[] { value }; // Required for VS design-time
return new object[] { Converter.ConvertBack(value, targetTypes[0], lastParameter, culture) };
}
}
#endregion
}
You are missing the BindingProxy that captures the DataContext:
public class BindingProxy : System.Windows.Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new PropertyMetadata(null));
}
XAML:
<TextBox.Resources>
<local:NumberBoxValueConverter x:Key="NumberBoxConverter"/>
<local:BindingProxy x:Key="proxy" Data="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</TextBox.Resources>
...
<local:Wrapper NumberBoxUsage1="{Binding Source={StaticResource proxy}, Path=Data.NumberBoxUsage,
Converter={StaticResource DebugDummyConverter}}" />
I have got a view model with a property:
public class MyModel
{
public bool IsEnabled {get;set;}
}
I want to use this property to toggle a button state. If the boolean is true I want to hide the button, and otherwise show it.
I tried things like:
<Button Visibility= "{Binding IsEnabled ? Hidden : Visible }">Enable</Button>
But this doesn't fit.
I tried some more complex solution but my guess is, I am missing something trivial.
Any suggestions?
Since you want to toggle between Hidden and Visible and true is hidden you can either write custom IValueConverter or use simple Style.Trigger
<Button Content="Enable">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
This is all assuming the DataContext is set accordingly and MyModel.IsEnabled raises INotifyPropertyChanged.PropertyChanged event whenever changed
public class MyModel : INotifyPropertyChanged
{
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
OnPropertyChanged("IsEnabled");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Use the BooleanToVisibilityConverter:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Button Visibility= "{Binding IsEnabled, Converter={StaticResource BooleanToVisibilityConverter}}" />
Add a class inheriting IValueConverter
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool bValue = (bool)value;
if (bValue)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility visibility = (Visibility)value;
if (visibility == Visibility.Visible)
return true;
else
return false;
}
#endregion
}
I have a view that I would like to assign a "backup" viewmodel to. Essentially if "Generic" is null I would like to set the DataContext to "GenericFactory". "GenericFactory" is able to create an instance of the "Generic" viewmodel. Upon creation the viewmodel is assigned to the appropriate property and the PropertyChanged event is fired, however given the code below the only DataContext I'm ever bound to is "GenericFactory". Can anyone explain and/or offer an alternative solution?
XAML
<Page x:Class="GenericProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:GenericProject.View">
<StackPanel>
<!--Additional markup-->
<vw:GenericView>
<vw:GenericView.Style>
<Style TargetType="{x:Type vw:GenericView}">
<Setter Property="DataContext" Value="{Binding Generic}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Generic}" Value="{x:Null}">
<Setter Property="DataContext" Value="{Binding GenericFactory}" />
</DataTrigger>
</Style.Triggers>
</Style>
</vw:GenericView.Style>
</vw:GenericView>
</StackPanel>
</Page>
ViewModel
public class MainPageViewModel : ViewModelBase
{
public GenericViewModel Generic
{
get { return _generic; }
private set
{
if (_generic != value)
{
_generic = value;
base.OnPropertyChanged("Generic");
}
}
}
public GenericFactoryViewModel GenericFactory { get; private set; }
private void OnGenericFactoryCreatedGeneric(object sender, CreatedGenericEventArgs e)
{
Generic = e.Generic;
}
public MainPageViewModel()
{
GenericFactory = new GenericFactoryViewModel();
GenericFactory.CreatedGeneric += OnGenericFactoryCreatedGeneric;
}
}
Thanks - Derrick
Thanks to XAMIMAX's comment I was able to find a solution using PriorityBinding.
XAML
<Page x:Class="GenericProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:GenericProject"
xmlns:vw="clr-namespace:GenericProject.View">
<Page.Resources>
<local:NullToDependencyPropertyUnsetConverter x:Key="NullToDependencyPropertyUnsetConverter" />
</Page.Resources>
<StackPanel>
<!--Additional markup-->
<vw:GenericView>
<vw:GenericView.DataContext>
<PriorityBinding>
<Binding Path="Generic" Converter="{StaticResource NullToDependencyPropertyUnsetConverter}" />
<Binding Path="GenericFactory" />
</PriorityBinding>
</vw:GenericView.DataContext>
</vw:GenericView>
</StackPanel>
</Page>
Converter
public class NullToDependencyPropertyUnsetConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value ?? DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I don't know how your factory works so this is probably not working code but you should be handling this logic in the view model, your view should just set the datacontext.
public GenericViewModel Generic
{
get
{
if(_generic == null)
{
GenericFactory.Create();
}
return _generic;
}
private set
{
if (_generic != value)
{
_generic = value;
base.OnPropertyChanged("Generic");
}
}
}
This will return null for Generic but when OnGenericFactoryCreatedGeneric is called it will set Generic and then cause the binding to update to a newly created view model.
If your factory has a synchronous create that returns the ViewModel then that would be better as Generic will never return null. _generic = GenericFactory.Create();
OK, the XAML is quite simple and uses MVVM to bind to an ICommand SomeCommand { get; } property on a view model:
<Button Command="{Binding Path=SomeCommand}">Something</Button>
If SomeCommand returns null, the button is enabled. (Nothing to do with CanExecute(object param) method on ICommand, because there is no instance to call that method on)
And now the question: Why is the button enabled? How would you work it around?
If you press the "enabled" button, obviously nothing is called. It is just ugly that the button looks enabled.
My colleague found an elegant solution: using a binding fallback value!
public class NullCommand : ICommand
{
private static readonly Lazy<NullCommand> _instance = new Lazy<NullCommand>(() => new NullCommand());
private NullCommand()
{
}
public event EventHandler CanExecuteChanged;
public static ICommand Instance
{
get { return _instance.Value; }
}
public void Execute(object parameter)
{
throw new InvalidOperationException("NullCommand cannot be executed");
}
public bool CanExecute(object parameter)
{
return false;
}
}
And then the XAML looks like:
<Button Command="{Binding Path=SomeCommand, FallbackValue={x:Static local:NullCommand.Instance}}">Something</Button>
The advantage of this solution is that it works better if you break Law of Demeter and you have some dots in the binding path, where each instance might become null.
It is enabled because that's the default state. Disabling it automatically would be an arbitrary measure that gives rise to other problems.
If you want to have a button without an associated command be disabled, bind the IsEnabled property to SomeCommand using an appropriate converter, e.g.:
[ValueConversion(typeof(object), typeof(bool))]
public class NullToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value !== null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Very similar to Jon's answer, you could use a style with a trigger to mark buttons which should be disabled when there is no command set.
<Style x:Key="CommandButtonStyle"
TargetType="Button">
<Style.Triggers>
<Trigger Property="Command"
Value="{x:Null}">
<Setter Property="IsEnabled"
Value="False" />
</Trigger>
</Style.Triggers>
</Style>
I prefer this solution because it addresses the problem very directly and it does not require any new types.