Caliburn.Micro treats hexadecimal values as wrong input - c#

I use Caliburn.Micro in my C# WPF MVVM application. I have TextBox in my application:
<TextBox Name="Param1" Grid.Row="1" Grid.Column="0"/>
and Param1 property in ViewModel:
public Byte Param1
{
get { return this._param1; }
set
{
this._param1 = value;
NotifyOfPropertyChange(() => this.Param1);
}
}
But when I try to input 8-bit hexadecimal value in this textbox (for example 1F, C0, 2A) then the borders of TextBox are colored in red. Probably Caliburn.Micro treats hexadecimal values as wrong input because decimal 8-bit values (for example: 101, 97, 64) are treated as correct input. How to make Caliburn.Micro treat hexadecimal values as correct input? How can I do it in my case? Your help will be appreciated highly.

Caliburn Micro does not provide by default an hexadecimal converter. You have to write your own converter. Something like:
[ValueConversion(typeof(byte), typeof(string))]
public class HexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
byte byteValue = (byte)value;
return byteValue.ToString("X", culture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
byte byteValue;
string stringValue = (string)value;
if (String.IsNullOrEmpty(stringValue))
{
return 0;
}
if (Byte.TryParse(stringValue, NumberStyles.AllowHexSpecifier, culture, out byteValue))
{
return byteValue;
}
return DependencyProperty.UnsetValue;
}
}
Now you have two options:
define a specific binding for the Param1 TextBox; in that binding you can use the HexConverter;
on the other side, if you need to use that converter all over your application, you can configure in the right way the ConventionManager class.
The first solution is quite simple, so I will show just the second one.
We have to say to Caliburn that we want to use the HexConverter each time that we have a named TextBox control "linked" to a byte property.
So we can do it in the Bootstrapper:
public class Bootstrapper : BootstrapperBase
{
private static IValueConverter hexConverter = new HexConverter();
public Bootstrapper()
{
Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
/* Show your starting view model */
DisplayRootViewFor<MainViewModel>();
}
protected override void Configure()
{
ConventionManager.ApplyValueConverter = (binding, dependencyProperty, propertyInfo) =>
{
if (dependencyProperty == UIElement.VisibilityProperty && typeof(bool).IsAssignableFrom(propertyInfo.PropertyType))
{
binding.Converter = ConventionManager.BooleanToVisibilityConverter;
return;
}
if (dependencyProperty == TextBox.TextProperty && typeof(byte).IsAssignableFrom(propertyInfo.PropertyType))
{
binding.Converter = hexConverter;
return;
}
};
}
}
As you can see, I set the ApplyValueConverter action, with an anonymous method which uses a BooleanToVisibilityConverter if the binded dependency property is the VisibilityProperty and the source property is a bool (this is Caliburn default).
Moreover if Caliburn is binding the Text dependency property to a byte, then it will use our HexConverter.
I hope it will help you.

Related

WPF ComboBox twoway databinding is not working as expected

I have an Products object with many properties. One of them is as follows
class Products : DependencyObject
{
public int ID
{
get { return (int)GetValue(IDProperty); }
set { SetValue(IDProperty, value); }
}
public static readonly DependencyProperty IDProperty = DependencyProperty.Register("ID", typeof(int), typeof(Products), new PropertyMetadata(0, new PropertyChangedCallback(IDChanged)));
private static void IDChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine($"New ID {e.NewValue}");
}
}
I have bound it with my combobox selecteditem property. The combobox itemsource is populated by a separate function which provides a list of Product IDs. As the IDs are not known initially it is set to default 0 at first.
The binding is done as follows
private void testClick(object sender, RoutedEventArgs e)
{
Products products = new Products();
ProductListCombobox.ItemsSource = GetProductIDList();
ProductListCombobox.SetBinding(ComboBox.SelectedItemProperty, new Binding { Source = items, Path = new PropertyPath(Products.IDProperty), Mode = BindingMode.TwoWay, Converter = new ProductsIDConverter(), ConverterParameter = ProductListCombobox });
}
The converter class for the binding
class ProductsIDConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var id = (int)value;
var cmb = (ComboBox)parameter;
if (!cmb.Items.Contains(id)) return cmb.Items[0];
else return id;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
Here in the converter class I set the first value of the items list to be the selecteditem which in turn should update the ID property on the source too as it is two way binding as far as I know. But it does not update the property back. But it updates if I manually change the selecteditem. I am pretty new to this databinding thing so if anyone cares to give some solution here will help me a lot.
Thanks

IValueConverter Using Lookup Collection

I am using a converter like this:
public class BreedConverter : IValueConverter
{
static ObservableCollection<Breed_> Breeds = Breed_.GetBreeds();
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && Breeds.Count > 0)
{
short breedID = (short)value;
Breed_ breed = Breeds.Single(s => s.BreedID == breedID);
return (string)breed.Breed;
}
else
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The Breeds collection is retrieved from a SQL Server database. I want to retrieve it once and then use it to do the conversion. I don't want to go to the database each and every time I need to convert.
Is there a better way to do this, e.g. ResourceDictionary (which I don't know how to use in this scenario, since I'm still a noob)?
This is what I have been able to get to work. As #Darthchai mentioned, I have created the collection in the class/window. However, I cannot figure out how to use INotifyPropertyChanged to let the specific TextBlock know to do the conversion. I am a noob, after all. :) What I was able to do - and I don't know if this is the right approach - was to use the Loaded event of the TextBlock to do the conversion. here is the code:
private void BreedTextBlock_Loaded(object sender, RoutedEventArgs e)
{
TextBlock textBlock = (TextBlock)sender;
if (textBlock != null && Breeds.Count > 0)
{
try
{
short breedID = short.Parse(textBlock.Text);
Breed_ breed = Breeds.Single(s => s.BreedID == breedID);
textBlock.Text = (string)breed.Breed;
}
catch
{
}
}
return;
}

Datepicker ValidationRules from code behind: validation rule not called on user input

I'm creating a wpf UserControl that contains a Datepicker. This datepicker is generated from code behind in c#.
public partial class EditorDatePicker : UserControl
{
public EditorDatePicker(TagEntry element, bool isTagPresent)
{
InitializeComponent();
// datepicker binding and validation
Binding binding = new Binding();
binding.Path = new PropertyPath("DateDict[" + element.ParentTag + element.ChildTag + "]");
binding.NotifyOnValidationError = true;
binding.ValidatesOnDataErrors = true;
binding.Converter = new DateTimeConverter();
binding.Mode = BindingMode.TwoWay;
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
binding.ValidationRules.Add(new DateValidationRule());
this.datePicker.SetBinding(DatePicker.SelectedDateProperty, binding);
}
class DateTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
try
{
DateTime test = (DateTime)value;
string date = test.ToString("d/M/yyyy");
return (date);
}
catch
{
return null;
}
}
return null;
}
The fact is that the validation rule is never called when I manualy enter a date in the DatePicker text field (But it's called when using the datepicker). The only thing I got is a FormatException on lost focus.
Any idea? Thanx.
One possibility is to use converter:
public class DateTimeNullConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider) => this;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTime)
return value.ToString();
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var text = value as string;
DateTime result;
if (text != null && DateTime.TryParse(text, out result))
return result;
return null;
}
}
You can use it like this to bind to public DateTime? DateTime property:
<TextBox Text="{Binding DateTime, Converter={local:DateTimeNullConverter}}" />
ConvertBack will be called on lost focus.

Check if Hex string is correct in Converter

I made a text box to input and show color Hex value.The binding is twoway to a color property of a parent.
Everything is working but, I need to make sure, in case I enter manually a Hex in the text box, and if this a not correct string, then use and display the current Hex value of the color, rather than trying to change it.
Here is what I tried but obviously it's not working, I'm a beginner and I have only a little experience with converter and WPF. If I write anything but not a valid Hex string, at the moment the textbox gets a red outline, but I wish that in this case, the Hex previous string reappears.
[ValueConversion(typeof(Color), typeof(String))]
public class ColorToStringConverter : IValueConverter
{
public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture)
{
Color colorValue = (Color)value;
return ColorNames.GetColorName(colorValue);
}
public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ColorHexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var hexCode = System.Convert.ToString(value);
//if (string.IsNullOrEmpty(hexCode))
// return null;
try
{
var color = (Color)ColorConverter.ConvertFromString(hexCode);
return color;
}
catch
{
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var hexCode = System.Convert.ToString(value);
Regex myRegex = new Regex("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
bool isValid = false;
if (string.IsNullOrEmpty(hexCode))
{
isValid = false;
}
else
{
isValid = myRegex.IsMatch(hexCode);
}
try
{
return hexCode;
}
catch
{
return null;
}
}
}
And the C# class for the TextBox
public class ColorHex : TextBox
{
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter)
{
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(this, TextProperty);
if (bindingExpression != null)
bindingExpression.UpdateSource();
}
}
}
And its xaml in Generic.xaml
<local:ColorHex x:Name="PART_ColorHex" Style="{StaticResource ColorPickerTextBox}" Text="{Binding SelectedColor, Converter={StaticResource ColorToHexConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ColorPicker}}}" />
Any idea ?
thank you
A valid hex color has the form '#nnn' or '#nnnnnn'.
So, the regex for this case would be: (^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)
NOw, you could add these lines of code:
var regex = #"(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)"
var match = Regex.Match(inputYouWantToCheck, regex,RegexOptions.IgnoreCase);
if (!match.Success)
{
// Color is not valid
}
Hope this helps.
What if you did something where you add a label next to the textbox to show an example of what color has been entered. You would just need to change the label color change each time.
I'd do it differently, using property to validate the color:
public Color Color { get; set; } = Colors.Red;
public string ColorText
{
get { return (new ColorConverter()).ConvertToString(Color); }
set
{
Color = (Color)ColorConverter.ConvertFromString(value);
OnPropertyChanged();
}
}
Bind to ColorText, it will throw in case it's wrong hex, and you can use ExceptionValidationRule to display it (as a red border):
<TextBox Text="{local:ExceptionBinding Path=ColorText}" />
where
public class ExceptionBinding : Binding
{
public ExceptionBinding() : base()
{
ValidationRules.Add(new ExceptionValidationRule());
}
}
As a bonus you can enter known colors as text, e.g. "Red":

Bind Flag enums to listbox containing checkboxes

I have flag enum say this -
[Flags]
public enum Department
{
None = 0,
A = 1,
B = 2,
C = 4,
D = 8
}
I want to show values of this enum on view. I thought of creating a listbox and binding its source to the collection of this enum List<Department> Departments.
All works so good until i thought of having a checkbox which binds to a property on my Viewmodel -
public Department SelectedDepartments { get; set; }
The solution here http://compilewith.net/2008/12/wpf-flagsenumvalueconverter.html provides elegant solution for binding enum values to checkboxes but its have one limitation of creating checkboxes equal to the number of enum values in list.
But, in my case i can't afford of having so many checkboxes lying on my UI since my Enum contains 20 values (so that means having 20 checkboxes on UI).
I tried using MultiBindingConverter but that fails in ConvertBack Method.
I want to bind the state of checkboxes with property SelectedDepartments. Say if property value is "A | B" then A and B checkbox should be checked whereas C and D should remain unchecked.
I don't think there's a way of doing this without using some code-behind.
I took the sample solution you linked to above, removed all of the CheckBoxes from MainWindow.xaml, added the following method to MainWindow.xaml.cs and called it from the MainWindow constructor:
private void AddCheckBoxes()
{
var converter = new FlagsEnumValueConverter();
foreach (Department dept in Enum.GetValues(typeof(Department)))
{
if (dept != Department.None)
{
var binding = new Binding()
{
Path = new PropertyPath("Department"),
Converter = converter,
ConverterParameter = dept
};
var checkBox = new CheckBox() { Content = dept.ToString() };
checkBox.SetBinding(CheckBox.IsCheckedProperty, binding);
DepartmentsPanel.Children.Add(checkBox);
}
}
}
This method does the work of creating all of the checkboxes, one for each named enum constant apart from None. I could then add further departments to the Department enum, rerun the solution and see additional checkboxes for the newly-added departments.
There were a few further minor changes that I had to make to this solution to get it working completely. You may or may not need to make these changes to your code. Firstly, I made the DataObject class implement INotifyPropertyChanged. Secondly, I rewrote the XAML in MainWindow.xaml as follows:
<StackPanel>
<StackPanel x:Name="DepartmentsPanel" />
<TextBlock Margin="5,20,0,0">
<TextBlock Text="Raw Value:" FontWeight="Bold" />
<TextBlock Text="{Binding Department}" />
</TextBlock>
</StackPanel>
(Basically, I wrapped the existing DepartmentsPanel in another StackPanel and moved the 'Raw Value' display into this outer StackPanel.) Finally, I set the DataContext of the whole MainWindow, rather than the DataContext of the DepartmentsPanel, to the DataObject created. This step was necessary to make the 'Raw Value' display work.
I have created an IValueConverter that supports binding to the enum directly without codebehind or helper classes. It has only two restrictions:
One converter instance has to be used for each source property. If the model contains more properties of the same enum type, each of them needs to use a separate converter instance. This can be done by instantiating the converters in the XAML.
When the enum type is a flags enum it has to be an Integer.
The solution is based on the empirical fact that there is always a Convert first before a ConvertBack. And there is always a Convert if a ConvertBack has changed the value. This one only works when the INotifyPropertyChanged is properly implemented on the model. So between the two calls the last known value can be stored in the converter and used in the ConvertBack method.
The converter instance should get the Type of the enum and whether it is a Flags enum or not.
<EnumToCheckedConverter x:Key="InstanceName" Type="{x:Type MyEnum}" Flags="True" />
Then the checkboxes can be bound using this converter.
<CheckBox Content="ValueText" IsChecked="{Binding Source, Converter={StaticResource InstanceName}, ConverterParameter=Value}"/>
Radio buttons can be bound by the same mechanism using an instance with Flags="False"
The source code of the converter
public class EnumToCheckedConverter : IValueConverter
{
public Type Type { get; set; }
public int? LastValue { get; private set; }
public bool Flags { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value.GetType() == Type)
{
try
{
var parameterValue = Enum.Parse(Type, parameter as string);
if (Flags == true)
{
var intParameter = (int)parameterValue;
var intValue = (int)value;
LastValue = intValue;
return (intValue & intParameter) == intParameter;
}
else
{
return Equals(parameterValue, value);
}
}
catch (ArgumentNullException)
{
return false;
}
catch (ArgumentException)
{
throw new NotSupportedException();
}
}
else if (value == null)
{
return false;
}
throw new NotSupportedException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null && value is bool check)
{
if (check)
{
try
{
if (Flags == true && LastValue.HasValue)
{
var parameterValue = Enum.Parse(Type, parameter as string);
var intParameter = (int)parameterValue;
return Enum.ToObject(Type, LastValue | intParameter);
}
else
{
return Enum.Parse(Type, parameter as string);
}
}
catch (ArgumentNullException)
{
return Binding.DoNothing;
}
catch (ArgumentException)
{
return Binding.DoNothing;
}
}
else
{
try
{
if (Flags == true && LastValue.HasValue)
{
var parameterValue = Enum.Parse(Type, parameter as string);
var intParameter = (int)parameterValue;
return Enum.ToObject(Type, LastValue ^ intParameter);
}
else
{
return Binding.DoNothing;
}
}
catch (ArgumentNullException)
{
return Binding.DoNothing;
}
catch (ArgumentException)
{
return Binding.DoNothing;
}
}
}
throw new NotSupportedException();
}
}

Categories

Resources