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":
Related
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;
}
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.
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.
Have the following simple xaml
<UserControl.Resources>
<converters:StateToColorConverter x:Key="stateToColorConverter"/>
</UserControl.Resources>
<StackPanel>
<Grid Width="150" Height="100" Background="{Binding State, Converter={StaticResource stateToColorConverter}}"></Grid>
<Button
Width="100"
Height="70"
Command="{Binding InitializeCommand}">Initialize</Button>
</StackPanel>
Its view model has a property State, which has a correct value.
public class MachineControlViewModel :ViewModelBase
{
private readonly IMachine machine;
public RelayCommand InitializeCommand { get; set; }
private MachineStates state;
public MachineStates State
{
get { return state; }
set { Set(() => State, ref state, value); }
}
public MachineControlViewModel(IMachine machine)
{
this.machine = machine;
InitializeCommand = new RelayCommand(Initialize, CanInitialize);
State = machine.State;
machine.StateChanged += MachineOnStateChanged;
}
// left out irrelevant parts
}
Then, the IValueConverter implementation
public class StateToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var state = (MachineStates) Enum.Parse(typeof (MachineStates), value.ToString());
switch (state)
{
case MachineStates.Idle:
return Color.Red;
case MachineStates.Initialized:
return Color.Green;
case MachineStates.Production:
return Color.Blue;
case MachineStates.Error:
return Color.Red;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
When I run my app, the backcolor of the Grid will not show.
When I set the BackColor hardcoded (so without the value converter), it is visualized correctly.
When I DO use my value converter, and put a breakpoint in the convert method, I can see that the code executes just fine, and the a Color is returned. But nothing is shown...
What am I doing wrong?
Background is of type Brush. Change in Converter to this
return new SolidColorBrush(Colors.Red);
Background property
The converter should return a Brush, not a Color, because that is the type of the Background property.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Brush brush = null;
switch ((MachineStates)value)
{
case MachineStates.Idle:
brush = Brushes.Red;
break;
case MachineStates.Initialized:
brush = Brushes.Green;
break;
case MachineStates.Production:
brush = Brushes.Blue;
break;
case MachineStates.Error:
brush = Brushes.Red;
break;
default:
break;
}
return brush;
}
It should especially not return a System.Drawing.Color (as you did), because that is WinForms, not WPF.
I have an issue when i binding a textbox to Propery and enter a negative number that is less than -1 - for example -0.45:
the textbox:
<TextBox Text="{Binding Txt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
and the property:
double txt;
public double Txt
{
get { return txt; }
set { txt = value; OnPropertyChanged("Txt"); }
}
it seems when i try to enter -0.54 it changes immediatly to 0, why?
Here is the convertor that does the job ( So leave your view model as it is- You can use it for both decimal and double). We just need to hold the decimal and -ve position initially:
public class DecimalConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value !=null)
{
return value.ToString();
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string data = value as string;
if (data == null)
{
return value;
}
if (data.Equals(string.Empty))
{
return 0;
}
if (!string.IsNullOrEmpty(data))
{
decimal result;
//Hold the value if ending with .
if (data.EndsWith(".") || data.Equals("-0"))
{
return Binding.DoNothing;
}
if (decimal.TryParse(data, out result))
{
return result;
}
}
return Binding.DoNothing;
}
}
So we hold the values or do nothing on binding
when you enter the decimal value it becomes 0 again so the best way to do this is using the lostfocus trigger:
<TextBox Text="{Binding Txt, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" Grid.Row="0"/>
Also you need to do this in the view model:
public double Txt
{
get { return txt; }
set
{
if (!txt.Equals(value))
{
txt = value;
OnPropertyChanged("Txt");
}
}
}