Decouple IValueConverter (Disable/Enable a WPF Converter) - c#

It's a way to enable and disable a WPF Converter? Either programmatically or directly from WPF binding a checkbox control to it.
I have this Textbox and Checkbox in my application:
When Checkbox is unchecked I can enter any numeric value, but when I Check the checkbox I want to enable this converter:
<TextBox
Grid.Row="1"
Grid.Column="1"
Margin="0,0,10,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
MaxLength="41"
Text="{
Binding Payload,
Mode=TwoWay,
Converter={StaticResource HexStringConverter},
UpdateSourceTrigger=PropertyChanged}"
/>
Also, this is the converter class:
public class HexStringConverter : IValueConverter
{
private string lastValidValue;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string ret = null;
if (value != null && value is string)
{
var valueAsString = (string)value;
var parts = valueAsString.ToCharArray();
var formatted = parts.Select((p, i) => (++i) % 2 == 0 ? String.Concat(p.ToString(), " ") : p.ToString());
ret = String.Join(String.Empty, formatted).Trim();
}
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
object ret = null;
if (value != null && value is string)
{
var valueAsString = ((string)value).Replace(" ", String.Empty).ToUpper();
ret = lastValidValue = IsHex(valueAsString) ? valueAsString : lastValidValue;
}
return ret;
}
private bool IsHex(string text)
{
var reg = new System.Text.RegularExpressions.Regex(#"^[0-9A-Fa-f\[\]]*$");
return reg.IsMatch(text);
}
}

As usual in WPF, there are many ways to do this.
One way is to use a trigger to change the binding given to Text, something like:
<TextBlock ....>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Payload}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=NameOfCheckBox}" Value="True">
<Setter Property="Text"
Value="{Binding Payload, Converter={StaticResource HexToStringConverter}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Another way is to use an IMultiValueConverter:
public class HexStringConverter : IMultiValueConverter
{
public object Convert (object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Length != 2 ||
values[0] is not string str ||
values[1] is not bool isEnabled)
{
return DependencyProperty.UnsetValue;
}
if (isEnabled)
{
// Do the actual conversion
}
else
{
return str;
}
}
}
Then:
<TextBlock ...>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource HexToStringConverter}">
<Binding Path="Payload"/>
<Binding Path="IsChecked" ElementName="NameOfCheckBox"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>

Related

Why isn't my binding for a TextBox validation rule parameter working?

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}}" />

WPF - Style bound to a property via ValueConverter not updating correctly

I have a Style converter defined as below:
public class StyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is MyStatus && targetType == typeof(Style))
{
var status = (MyStatus)value;
switch (status)
{
case MyStatus.First:
return Application.Current.FindResource("firstStyle");
case MyStatus.Second:
return Application.Current.FindResource("secondStyle");
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In App.xaml I've got some styles defined as below:
<Style x:Key="firstStyle" TargetType="Border">
<Setter Property="Background" Value="Yellow" />
</Style>
<Style x:Key="secondStyle" TargetType="Border">
<Setter Property="Background" Value="LightGreen" />
</Style>
And in Window.xaml:
<MyApp:StyleConverter x:Key="StyleConverter" />
<DataTemplate DataType="{x:Type MyApp:Item}">
<Border x:Name="ItemBorder"
Style="{Binding Path=Status, Mode=OneWay, Converter={StaticResource StyleConverter}}">
<!-- some content here -->
</Border>
</DataTemplate>
<ItemsControl x:Name="MyItems" />
Item:
public class Item : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private MyStatus status;
public MyStatus Status {
get
{
return status;
}
set
{
status = value;
PropertyChanged(this, new PropertyChangedEventArgs("Status"));
}
}
}
I'm adding Item instances to ObservableCollection<Item> collection which is bound to MyItems (and MyStatus is a simple enum)
My problem is that the style is applied properly only for the first time and is not changing after I change the Status property of Item

DateTime need convert to string wpf

<DataGrid....
<DataGrid.Resources>
<DataTemplate DataType="{x:Type DateTime}">
<TextBlock Text="{Binding StringFormat={}{0:d}}" />
</DataTemplate>
</DataGrid.Resources>
...
<DataGridTemplateColumn Header="Время оплаты">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock VerticalAlignment="Center" Text="{Binding date_payment}" Width="Auto" Height="Auto" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
but this column have type DateTime...
i need convert this column on this type(string), because i need rename value row on this column, i use event LoadingRow so
DataRowView item = e.Row.Item as DataRowView;
DataRow row = item.Row;
var time = row[4];
if (Convert.ToString(time) == "01.01.0001 0:00:00")
{
row[4] = "No payment";
}
but its wrong, row no converting to string, please help
First of all, you have both a cell template and a data template. Pick one. Second, since you have a data template anyway there's no reason to create a converter, much less a code-behind event handler. You can keep all the relevant code and text strings (what if you need to localize?) nicely in one place with a trigger:
<DataTemplate TargetType="{x:Type DateTime}">
<TextBlock x:Name="text" Text="{Binding StringFormat={}{0:d}}"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Static DateTime.MinValue}">
<Setter TargetName="text" Property="Text" Value="No payment"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
In case if it's Nullable value you could use
Binding="{Binding date_payment, TargetNullValue={}Дата отсутствует}"
In case if not, use IValueConverter where you will check for MinDate.
Here is an Example how to use converters, and converter for you
public class DateConverter:IValueConverter
{
private const string NoDate = "Дата отсутствует";
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(value is DateTime)
{
var date = (DateTime) value;
if(date==DateTime.MinValue)
return NoDate;
return date.ToString();
}
return NoDate;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You shoud use Converter for this:
public class MyConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (value == DateTime.MinValue) {
return "No payment";
}
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
Than just add the converter to your binding.

WPF Trigger that would work if the value is equal or greater

I wrote an application in WPF that has a button and slider. I would like to create a trigger for the button, which would set the button's 'IsEnable' property to false when the slider value is greater than another value.
Right now I have:
<Style x:Key="zoomOutButton" TargetType="Button" BasedOn="{StaticResource ResourceKey=buttonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentAltitude}" Value="24000">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
But I would like to set isEnable not when the value of CurrentAltitude equal 24000, but when it is equal or greater than 24000.
Any ideas?
You can achieve this using a converter:
public class IsEqualOrGreaterThanConverter : IValueConverter {
public static readonly IValueConverter Instance = new IsEqualOrGreaterThanConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
int intValue = (int) value;
int compareToValue = (int) parameter;
return intValue >= compareToValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Then your trigger will look like this:
<Style x:Key="zoomOutButton" TargetType="Button" BasedOn="{StaticResource ResourceKey=buttonStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentAltitude, Converter={x:Static my:IsEqualOrGreaterThanConverter.Instance}, ConverterParameter=24000}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
A more generic converter, usable with any comparable type, could be :
public class IsGreaterOrEqualThanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
IComparable v = value as IComparable;
IComparable p = parameter as IComparable;
if (v == null || p == null)
throw new FormatException("to use this converter, value and parameter shall inherit from IComparable");
return (v.CompareTo(p) >= 0);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
But in this case, the ConverterParameter must be interpreted with the same type as the value transmitted to your Converter. For example, to compare an int property 'MyIntProperty' with the contant int value 1, in your XAML, you can use this syntax :
<UserControl x:Class="MyNamespace.MyControl"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:genconverters="clr-namespace:MyConverterNamespace;assembly=MyConvertersAssembly">
<Grid>
<Grid.Resources>
<genconverters:IsGreaterOrEqualThanConverter x:Key="IsEqualOrGreaterThanConverter"/>
<sys:Int32 x:Key="Int1">1</sys:Int32>
</Grid.Resources>
<ComboBox IsEnabled="{Binding MyIntProperty,
Converter={StaticResource IsEqualOrGreaterThanConverter},
ConverterParameter={StaticResource Int1}}"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"/>
</Grid>

Change true/false to a image

i have a column in a datagrid that the content is True/false, how can i change this true/false(boolean) to a image, according to the text?
I'm using c# wpf.
Edit:
<dg:DataGridTemplateColumn MinWidth="70" Header=" Is Done2">
<dg:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Name="imgIsDone" Source="../Resources/Activo.png"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsDone}" Value="False">
<Setter TargetName="imgIsDone" Property="Source" Value="../Resources/Inactivo.png"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</dg:DataGridTemplateColumn.CellTemplate>
</dg:DataGridTemplateColumn>
public class BoolToImage : IValueConverter
{
public Image TrueImage { get; set; }
public Image FalseImage { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is bool))
{
return null;
}
bool b = (bool)value;
if (b)
{
return this.TrueImage;
}
else
{
return this.FalseImage;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then in your xaml, as a resource:
<local:BoolToImage TrueImage="{StaticResource Image}" FalseImage="{StaticResource FalseImage}" x:Key="BoolImageConverter"/>
Then in your binding:
ImageSource={Binding Path=BoolProp,Converter={StaticResource BoolImageConverter}}"
Use a DataGridTemplateColumn to supply a DataTemplate for the column that contains an Image, and use a value converter or a data trigger to set the image source based on the value of the column. Here is an example that uses a data trigger:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Name="MyImage" Source="TrueImage.png"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding BoolColumn}" Value="False">
<Setter TargetName="MyImage" Property="Source" Value="FalseImage.png"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
In case someone struggles getting the answer(from benPearce) running, i had to modify the converter to use ImageSource!
using System;
using System.Windows.Data;
using System.Windows.Media;
namespace ViewManager
{
public class BoolToImageConverter : IValueConverter
{
public ImageSource TrueImage { get; set; }
public ImageSource FalseImage { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is bool))
{
return null;
}
bool b = (bool)value;
if (b)
{
return this.TrueImage;
}
else
{
return this.FalseImage;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
usage with a resource
<local:BoolToImageConverter x:Key="BoolToImageConverter" FalseImage="{StaticResource UnLockedSource}" TrueImage="{StaticResource LockedSource}" />
...
<Button Grid.Column="2" Command="{Binding LockUnlockCommand}" >
<Image Source="{Binding IsLocked, Converter={StaticResource BoolToImageConverter}}" MinHeight="50" MinWidth="50" />
</Button>

Categories