I have setup binding as follows
XAML
<TextBlock Text="{Binding Path=Color, Converter={StaticResource ColorToStringConverter}}" />
C#: Showing what Color is
public System.Windows.Media.Color Color
{
get
{
var color = new HSLColor { Hue = this.Hue, Saturation = this.Saturation, Luminosity = this.Luminosity };
string strColor = color.ToRGBString();
return new System.Windows.Media.Color {
R = byte.Parse(strColor.Substring(0, 2), System.Globalization.NumberStyles.HexNumber),
G = byte.Parse(strColor.Substring(2, 2), System.Globalization.NumberStyles.HexNumber),
B = byte.Parse(strColor.Substring(4, 2), System.Globalization.NumberStyles.HexNumber)
};
}
set { SetValue(ColorProperty, value); }
}
Converter
public class ColorToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Color color = (Color)value;
return color.ToString();
}
}
But my converter is getting value like
value = "{Name=0, ARGB=(0, 0, 0, 0)}"
I'd expect it to be a System.Windows.Media.Color why am I getting this?
Basically, I have 3 Silders for HSL values bound to DependencyProperties, each have a PropertyChangedCallback attached to them
new PropertyChangedCallback(HSLValuePropertyChanged)
It looks like
protected void HSLValueChanged()
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Color"));
}
Basically its purpose is to update controls bound to the dependency property Color. The idea is that get should run for property Color which creates a new color from HSL properties. The problem it seems is that the get does not run even when I change HSL values.
UPDATE
So I tried to return just value in the case of an exception, I got nothing in the textbox, so i did value.toString() got Color [Empty] all the time. What did I do wrong?
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
Color color = (Color)value;
return color.ToString();
} catch (Exception) {
return value.ToString();
}
}
You seem to be using the ColorDependencyProperty.
If so, I think the binding may never actually go trough the accessors (get/set) ...
You should set the new color (in the dependencyProperty via the setvalue method) on each of your H/S/L properties change call back...
I hope i am clear :-)
When bound on a DependencyProperty, bindings dont use property getters to obtain values, so, all the code you put in the Color property getter is skipped as far as bindings are concerned.
Stick to defining your getters as get { return (XXX)GetValue(MyProperty); } and find another way around.
Related
I know that you can limit the input characters of TextBox from user by setting MaxLength property.
Is there a similar way to limit the number of characters shown in Text when the Text is updated with Binding? For example, when it is updated from Binding just show the first 5 characters and leave the rest?
Update:
Thanks for all the info, I got inspired by your recommendation and in the end did it with a converter. Here is how I did it, if someone wants to use it later.
public class StringLimiter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string val = value.ToString();
if (val.Length < 5)
return val;
else
return val.Substring(0, 5);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string val = value.ToString();
if (val.Length < 5)
return val;
else
return val.Substring(0, 5);
}
}
This should work:
Xaml:
<TextBox Text="{Binding TextToDisplay}" />
Code:
private const int maxLength = 5;
private string _textToDisplay = "Hello SO";
public string TextToDisplay
{
get
{
if(_textToDisplay.Length > maxLength)
{
return _textToDisplay.Substring(0, maxLength);
}
return _textToDisplay;
}
set
{
_textToDisplay = value;
RaiseProperyChanged();
}
}
I hope to understand you right. You could create a new Property in the ViewModel that returns only the first 5 chars of the text and set your binding to that property.
You might need to call PropertyChanged for the new Property when the text changes.
A simple but very flexible way of doing it would be to introduce a projected property in your Viewmodel that returns the first 5 characters of the original property and then bind your control to this property. Since you're only showing part of the property value, I assume that you don't want to write to this property from that TextBox. So make you projected property read-only too.
I'm not new in C# programming with WPF and I've never needed to do this, but now I need it and I'm stuck for some time now with it. I need to bind an enum that has attached it's OnPropertyChanged method to raise a converter every time the enum changes. I've got the following code for the enum:
private WindowState windowstate;
public enum WindowState
{
INITIAL = 0,
LANGUAGE = 1,
SENSOR = 2,
PARAMETERS = 3,
LEGAL = 4,
PRIVACY = 5,
ABOUT = 6,
MANUAL = 7
}
public WindowState State
{
get { return windowstate; }
set { windowstate = value; OnPropertyChanged("State"); }
}
And on the xaml where I bind the enum I've got this:
Color="{Binding State, Converter={StaticResource ButtonMenuColor}, ConverterParameter=language, ElementName=userControl}"
What I want is to change the color of a button depending on the value of the enum. Is it possible to make it this way or WPF, for some reason, does not support this?
This is the converter code:
class ButtonMenuColor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Lynx.Windows.Herramientas.WindowState state = (Lynx.Windows.Herramientas.WindowState)value;
string param = parameter as string;
if (state.ToString().ToLower() == param)
return Application.Current.FindResource("white") as SolidColorBrush;
return Application.Current.FindResource("buttonmenu_color") as SolidColorBrush;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
what come into my head are :
change binding mode to two way. add updateSourceTrigger =
PropertyChanged , NotifySourceUpdated = True
try a fallback value checking your binding is correct or not.
maybe your control loads before your value set.
and put your enum value as
{x:static Namespace:Class.WindowState+LANGUAGE }
Seems you are binding to a user control, but your property is in a viewmodel?
So change your binding to
Color="{Binding DataContext.State, Conv...}"
So you are binding to the State property of your userControl's viewmodel. If the State is a DependencyProperty of your userControl the binding should work.
I am trying to change the background color of the spinner with binding BackgroundColor property in as follows, but it is having no effect.
View.axml
<mvvmcross.droid.support.v7.appcompat.widget.MvxAppCompatSpinner
android:layout_width="115dp"
android:layout_height="match_parent"
android:textColor="#color/primary_text"
local:MvxItemTemplate="#layout/single"
local:MvxBind="ItemsSource SingleList; SelectedItem SingleSize ; BackgroundColor SingleBackgroundValueConverter(IsSingleValid)" />
Converter.cs
public class SingleBackgroundValueConverter: MvxValueConverter<bool>
{
protected override MvxColor Convert(bool value, object parameter, CultureInfo culture)
{
// either white or red
return value ? new MvxColor(255, 255, 255) : new MvxColor(255, 0, 0);
}
}
In the following, I was able to see the alert pop up, but background color does not change at all.
ViewModel.cs
public void Save()
{
if (!isExist)
{
OnExit(this, null);
}
else
{
_isSingleValid= false;
RaisePropertyChanged(() => IsSingleValid);
Mvx.Resolve<IUserDialogs>().Alert("It is not valid");
}
}
private bool _isSingleValid = true;
public bool IsSingleValid
{
get { return _isSingleValid; }
set
{
_isSingleValid= value;
RaisePropertyChanged(() => IsSingleValid);
}
}
BackgroundColor is part of the Color pluign.
The first step is to make sure you have installed it.
Install-Package MvvmCross.Plugin.Color
And then inherit your converter from MvxColorValueConverter<T>.
public class SingleBackgroundValueConverter : MvxColorValueConverter<bool>
{
protected override MvxColor Convert(bool value, object parameter, CultureInfo culture)
{
return value ? new MvxColor(255, 255, 255) : new MvxColor(255, 0, 0);
}
}
Then you have to change your converter name in the binding, because mvvmcross naming convention strips off the ValueConverter part.
local:MvxBind="ItemsSource SingleList; SelectedItem SingleSize ; BackgroundColor SingleBackground(IsSingleValid)"
The issue is there is no BackgroundColor property on a MvxAppCompatSpinner (AppCompatSpinner properties).
An alternate property that you can use is Background. However, Background requires a Android.Graphics.Drawables.Drawable and not a Android.Graphics.Color.
Therefore you will need to create a converter specifically for Android platform to return a Android.Graphics.Drawables.ColorDrawable:
public class SingleBackgroundValueConverter : MvxValueConverter<bool, ColorDrawable>
{
protected override ColorDrawable Convert(bool value, System.Type targetType, object parameter, CultureInfo culture)
{
return value ? new ColorDrawable(new Color(255, 255, 255)) : new ColorDrawable(new Color(255, 0, 0));
}
}
And then in your layout:
<mvvmcross.droid.support.v7.appcompat.widget.MvxAppCompatSpinner
android:layout_width="115dp"
android:layout_height="match_parent"
android:textColor="#color/primary_text"
local:MvxItemTemplate="#layout/single"
local:MvxBind="ItemsSource SingleList; SelectedItem SingleSize ; Background SingleBackground(IsSingleValid)" />
Note - Using MvxValueConverter
When using the MvxValueConverter in your XML/AXML you must make sure not to include the 'ValueConverter' part of the converter name:
Error
local:MvxBind="ItemsSource SingleList; Background SingleBackgroundValueConverter(IsSingleValid)" />
You will see an error message in the ouput/logcat such as:
MvxBind:Error: 3.98 Failed to find combiner or converter for
SingleBackgroundValueConverter
Working
local:MvxBind="ItemsSource SingleList; Background SingleBackground(IsSingleValid)" />
Side Note - Suggestion
In your ViewModel sample code Save() method you are assigning the _isSingleValid backing field and then manually raising the change RaisePropertyChanged(() => IsSingleValid);. You can simplify this code by just assigning the property directly IsSingleValid = false; as the set of the property will execute the RaisePropertyChanged(() => IsSingleValid);. The only time you need to assign to the backing field should be if there is some additional logic in the setter that you don't want to run when updating the property or if you don't want to raise a changed event.
I am working on a WPF app. In this app, I have some XAML segments. I need to display the XAML segments in a TextBlock. In my XAML, I have the following line:
<TextBlock Text="{Binding Path=XamlSegment, Converter={StaticResource XamlToTextConverter}}" />
The XamlSegment property will have a value like "-0.275*x2". In an attempt to render this XAML in my UI so that the Superscript shows, I'm using the XamlToTextConverter, which is defined as follows:
namespace MyApp.Converters
{
public class XamlToTextConverter : IValueConverter
{
private static readonly Regex Regex = new Regex("(<.*?)>(.*)(</.*?>)", RegexOptions.Compiled);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// value looks like this: -0.275*x<Run Typography.Variants="Superscript">2</Run>
var xamlText = value as string;
if (xamlText != null)
{
try
{
xamlText = "<TextBlock>" + xamlText + "</TextBlock>";
var xamlTextWithNamespace = Regex.Replace(xamlText, "$1 xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">$2$3");
return XamlReader.Parse(xamlTextWithNamespace);
}
catch (Exception)
{
return value;
}
}
else
{
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
When this converter gets ran, my UI shows "System.Windows.Controls.TextBlock" instead of the rendered XAML. Yet, I don't know why. How do I get my XamlSegment to render in my UI?
Thanks
The Text property of the TextBlock will be set to a TextBlock object according to your XamlToTextConverter. Since the Text property should be type of string, it doesn't know how to show a TextBlock as string. So the default way to get thing done is to use the ToString method on TextBlock to fill the Text property, which makes the value of Text as "System.Windows.Controls.TextBlock".
It seems that you would like to dynamically render the xaml. You could reference this link(Loading XAML XML through runtime?) for a solution.
N.B.: THIS IS NOT JUST ABOUT THE CUSTOM MARKUP EXTENSIONS. PLEASE READ BEFORE MARKING AS DUPLICATE.
I have a WPF markup extension with a converter, and the two of them go as follows:
[ValueConversion(typeof(WindowState), typeof(object))]
internal class WindowStateToObjectConverter : IValueConverter {
public WindowStateToObjectConverter() { }
public WindowStateToObjectConverter(object maximized, object normal) {
this.maximized = maximized;
this.normal = normal;
}
#region Properties
#region Maximized Property
private object maximized;
public object Maximized {
get { return maximized; }
set { maximized = value; }
}
#endregion
#region Normal Property
private object normal;
public object Normal {
get { return normal; }
set { normal = value; }
}
#endregion
#endregion
public object Convert(object value, Type targetType, object param, CultureInfo culture) {
if((value as WindowState? ?? WindowState.Normal) == WindowState.Maximized) return maximized;
else return normal;
}
public object ConvertBack(object value, Type targetType, object param, CultureInfo culture) {
throw new InvalidOperationException("Cannot convert downwards to WindowState");
}
}
[MarkupExtensionReturnType(typeof(Binding))]
internal class WindowMaximizedSwitchExtension : MarkupExtension {
object maximized, normal;
public WindowMaximizedSwitchExtension(object maximized, object normal) {
this.maximized = maximized;
this.normal = normal;
}
public override object ProvideValue(IServiceProvider serviceProvider) {
Binding ret = new Binding("WindowState");
RelativeSource retRSource = new RelativeSource(RelativeSourceMode.FindAncestor);
retRSource.AncestorType = typeof(Window);
ret.RelativeSource = retRSource;
ret.Converter = new WindowStateToObjectConverter(maximized, normal);
return ret.ProvideValue(serviceProvider);
}
}
They are for a custom window I am designing - they will be used to switch certain values (border width, margins, etc.) when the window is maximized. However, at design-time, they always return null, which is a real pain, because then my window looks like this:
...when it's supposed to look like this:
(Ignore that the title and icon are missing in the first image, although, if you have a solution, feel free to answer - it's essentially the same problem.)
For obvious reasons, it would be extremely hard to design with this. The only major issue you see with the window preview is the places where I've used the extension to set Row/ColumnDefinitions - when it returns null, the Height/Width is set to 1*. So, my question is whether there is a way to select a default value, perhaps instead of the binding (e.g. the non-maximized value), at design time.
Well, I feel like an idiot, but I found the solution fairly quickly:
In the ProvideValue method of the expression, I added the following line:
ret.FallbackValue = normal;
Where normal is the value to use when the window is not maximized.
ProvideValue now looks like this:
public override object ProvideValue(IServiceProvider serviceProvider) {
Binding ret = new Binding("WindowState");
RelativeSource retRSource = new RelativeSource(RelativeSourceMode.FindAncestor);
retRSource.AncestorType = typeof(Window);
ret.RelativeSource = retRSource;
ret.Converter = new WindowStateToObjectConverter(maximized, normal);
ret.FallbackValue = normal;
return ret.ProvideValue(serviceProvider);
}
This returns the normal value during design-time.