I have this Class:
public class MyData
{
public static int Total Files;
public static int Total FilesFinished;
}
And I have simple Progress-Bar that calculate its Value this way:
double value = ((double)MyData.FilesFinished / MyData.Files) * 100;
And update my Label using simple Timer:
Label name="lblPercentage" />
lblPercentage.Content = value;
Now I want to use Converter instead of updating my Label via code behind.
So I have this class (not implemented yet):
public class TotalFilesToTotalPercentageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Inside my Window.Resource I have this:
<Convertors:TotalFilesToTotalPercentageConverter x:Key="FilesToPercentageConverter "/>
And this is what I have try inside my Label:
Content="{Binding Converter={StaticResource FilesToPercentageConverter}}"
So my problem is that I try to see if my TotalFilesToTotalPercentageConverter class is responding via the debugger and it seems not, nothing happening.
What did I do wrong?
Update
I forget to mention that my TotalFilesToTotalPercentageConverter class in inside Converter folder under Utils folder under Classes folder
You need to bind the Content property to a source property for your Convert method to be invoked. Converters only work with data bindings.
This means that instead of setting the Content property of the Label in the code-behind like this:
lblPercentage.Content = value;
You should set a source property of a view model that you then bind the Content property of the Label to:
Content="{Binding Path=YourValueProperty, Converter={StaticResource FilesToPercentageConverter}}"
Set the DataContext of your view to an instance of your view model class:
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
The view model class needs to implement the INotifyPropertyChanged and raise the PropertyChanged event in the setter of the source property (YourValueProperty).
Related
This question already has an answer here:
Databindings don't seem to refresh
(1 answer)
Closed 5 years ago.
In my UWP app I have a TextBlock, which should display a (formatted) date, that is bound to a property in the view model:
<TextBlock Style="{StaticResource summaryTextStyleHighlight}" Margin="0,10,0,0"
Text="{x:Bind ViewModel.CurrentDisplayDay, Converter={StaticResource DateFormatConverter}, ConverterParameter=d, Mode=OneWay}"
Name="lblCurrentDate" />
The converter is "configured" in the XAML like this:
<local:DateFormatConverter x:Key="DateFormatConverter" />
And the converter class is as followed:
public class DateFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
return null;
DateTime dt = DateTime.Parse(value.ToString());
if (parameter != null)
{
return dt.ToString((string)parameter, Utils.GetCurrentCultureInfo());
}
return dt.ToString("g", Utils.GetCurrentCultureInfo());
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
The ViewModel has a simple property for the DateTime value that is bound:
public DateTime CurrentDisplayDay
{
get;
private set;
}
But when I update the value in the ViewModel, the value won't get updated in the TextBlock on the (Main)Page.
I tried to move the property to the page, but that didn't help. If I refresh the page (navigate to it again), then the updated value is being displayed but I don't want to navigate to it, it should show the updated value through the binding.
What could be the issue?
#Patric You seem to be doing almost everything correctly, but you have forgotten about one step.
Is there any notification indicating that your property has been updated, when its value changes? You need to propagate a notification to the UI indicating that your ViewModel property has been altered, because otherwise even though the Text Dependency property is actively listening for any notification from the "source" (you have defined the binding as One-Way), you are simply not communicating anything to it.
Your ViewModel should implement the INotifyPropertyChanged Interface, which exposes the PropertyChanged event.
The property changed event will be responsible for communicating the update.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
On your property setter, you simply have to invoke this method, which will consequently be responsible for invoking the PropertyChanged event, with the appropriate event data.
Edit:
In order to use the CallerMemberName Attribute (which allows you to get the name of the property which called the method) use the following namespace System.Runtime.CompilerServices
I have written a control with a bindable property. This control also has a method to modify that property's value:
public class MyControl : ContentView // WPF: inherited from UserControl
{
// Xamarin:
public static readonly BindableProperty MyValueProperty = ...
// WPF:
// public static readonly DependencyProperty MyValueProperty = ...
public int MyValue
{
get { return (int) GetValue(MyValueProperty); }
set { SetValue(MyValueProperty, value); }
}
public void Reset()
{
MyValue = 0;
}
}
I am using that control in a normal XAML page and update MyValue via binding:
<local:MyControl x:Name="TheControl"
MyValue="{Binding MyValueSource, Mode=OneWay}" />
The binding initially propagates changes from MyValueSource to MyValue. But as soon as I call the Reset() method once, the binding is overwritten by the 0 and updates to MyValueSource are no longer pulled.
I suppose any direct assignment of MyValue is intended to replace a OneWay binding. With a TwoWay binding, the change is just propagated back to MyValueSource and the binding remains functional.
If Reset() was in the view model, I could do this:
public void Reset()
{
// TheControl.MyValue = 0; // Bad practice, destroys the binding
MyValueSource = 0; // Good practice, preserves the binding
}
I don't want to implement the reset logic (which is more complex than in this reduced example) in every VM though, so it's located in the view/control.
So I wonder - can you assign a bindable property's value from the control's code behind and still preserve a possible OneWay binding? I know this means the VM does not get the changed value; binding OneWay is likely not correct if the control updates the property as well; you should rather use a TwoWay binding then.
But if someone says OneWay in XAML, I'd rather have it behave that way down to the wire than implement some "OneWay until you call Reset()" behavior.
Side note: I am working in Xamarin, but I guess the behavior is the same for WPF.
Taken and fleshed out from #Clemens' comment:
WPF
You can use the SetCurrentValue method on an DependencyObject (i. e. the control) to change the current effective value of a DependencyProperty. Unlike SetValue, with SetCurrentValue any triggers, data bindings and styles to that property remain intact.
public void Reset()
{
// this.SetValue(MyValueProperty, 0); // Replaces the binding
this.SetCurrentValue(MyValueProperty, 0); // Keeps the binding
}
Remember that if you defined a OneWay binding, the view model will not be notified about the changed value, and that any change to the VM's MyValueSource property will override the control's value again (if the property is implemented correctly).
Xamarin
There is currently no proper way to assign a BindableProperty's value without replacing a OneWay binding attached to it. BindableObject (the control's base class) does not have any method comparable to WPF's SetCurrentValue and SetValue will allways replace the binding.
However, if you change the binding to BindingMode.TwoWay, the internal value change is propagated back to the view model. You should probably do this anyway to keep the control and the VM synchronized.
public void Reset()
{
// Replaces any OneWay bindings
// Updates MyValueSource for TwoWay bindings
this.SetValue(MyValueProperty, 0);
}
Here is the Hacky WPF equivalent for Xamarin, for OneWay binding:
public static class BindingObjectExtensions
{
public static Binding GetBinding(this BindableObject self, BindableProperty property)
{
if (self == null)
{
throw new ArgumentNullException(nameof(self));
}
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}
var methodInfo = typeof(BindableObject).GetTypeInfo().GetDeclaredMethod("GetContext");
var context = methodInfo?.Invoke(self, new object[] { property });
var propertyInfo = context?.GetType().GetTypeInfo().GetDeclaredField("Binding");
return propertyInfo?.GetValue(context) as Binding;
}
public static void SetCurrentValue(this BindableObject self, BindableProperty property, object value)
{
if (self == null)
{
throw new ArgumentNullException(nameof(self));
}
if (property == null)
{
throw new ArgumentNullException(nameof(property));
}
var backupBinding = self.GetBinding(property);//backup binding
var backupConverter = backupBinding.Converter;//backup orig. converter
self.SetValue(property,value);//removes the binding.
backupBinding.Converter = new DefaultValueConverter {DefaultValue = value};//change the converter
self.SetBinding(property, backupBinding);//target should be updated to the default value
var converterField = backupBinding.GetType().GetTypeInfo().GetDeclaredField("_converter");
converterField.SetValue(backupBinding, backupConverter);//restore the converter
}
}
//the default value converter class
[ContentProperty(nameof(DefaultValue))]
public class DefaultValueConverter : BindableObject, IValueConverter, IMarkupExtension<DefaultValueConverter>
{
public object DefaultValue
{
get => GetValue(DefaultValueProperty);
set => SetValue(DefaultValueProperty, value);
}
public static readonly BindableProperty DefaultValueProperty =
BindableProperty.Create(nameof(DefaultValue), typeof(object), typeof(DefaultValueConverter));
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return DefaultValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return DefaultValue;
}
public DefaultValueConverter ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return ((IMarkupExtension<DefaultValueConverter>) this).ProvideValue(serviceProvider);
}
}
<!-- View -->
<TextBox Text="{Binding str, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
// View Model
private string _str;
public string str
{
get { return _str; }
set
{
if (!value.Contains("a"))
_str = value;
OnPropertyChanged(nameof(str));
}
}
When typing in the TextBox I want it to throw out any invalid characters (in this sample case the letter 'a', but it could really be for anything). For example:
User types 'fds' followed by an 'a'
str detects a, so it doesn't set _str to 'fdsa', keeping it at 'fds' but raises the event anyway to indicate to the view to throw out the 'a'.
In WPF, this results in the textbox containing 'fds'. In UWP, this results in the textbox incorrectly containing 'fdsa' still.
It appears that in UWP when a control has focus, it will not respect the TwoWay binding.
I can create a button that has a Click event that when pressed will update my TextBox correctly.
private void btn_Click(object sender, RoutedEventArgs e)
{
OnPropertyChanged(nameof(str));
}
We have many ViewModels that we need to use in both WPF and UWP views, and we have this required behavior all over the place. What is a good solution to this problem?
* EDIT *
Came back to the problem after the weekend and it seems to have fixed itself. I have no idea why. I am closing the question for now.
You could use a converter to solve your problem, you could elaborate a better converter, in my example I just use a silly converter to demonstrate my idea.
Converter:
public class Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
var someString = value.ToString();
return someString.Replace("a", "");
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
XAML
<TextBox Text="{Binding Str, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource converter}}"/>
You could use an attached behavior also.
I'm trying to bind the background colour of my CardView to my view model, but I'm getting this error back from Mvx:
MvxBind:Warning: 11.66 Failed to create target binding for binding
CardBackgroundColor
I'm not sure whether I'm using the wrong property binding in the AXML or in the view model.
Here is the property I'm trying to bind it to in the view model:
public int EventEntryBackgroundColour
{
get
{
return IsRead
? Resource.Color.yellow
: Resource.Color.White;
}
}
I've also tried using it as a string type to return a colour in HEX, but it still doesn't work.
Here's the attribute, I'm setting on the CardView
cardview:MvxBind="CardBackgroundColor EventEntryBackgroundColour"
Any help with this would be much appreciated.
I faced the same issue. My solution...
Converter:
public class MessageStatusToColorDrawableConverter : MvxValueConverter<bool, ColorDrawable>
{
protected override ColorDrawable Convert(bool value, Type targetType, object parameter, CultureInfo cultureInfo)
{
var context = Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity; // To get the context of the activity
return value ? new ColorDrawable(new Color(ContextCompat.GetColor(context, Resource.Color.Pink))) : new ColorDrawable(new Color(ContextCompat.GetColor(context, Resource.Color.Green)));
}
}
XML:
<RelativeLayout
android:id="#+id/relay_archive"
android:layout_width="10dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:paddingLeft="10dp"
android:paddingRight="10dp"
local:MvxBind="Background MessageStatusToColorDrawable(Status)">
We can't bind an Android.Graphics.Color property to local:MvxBind="BackgroundColor... since the BackgroundColor property wants you to use the NativeColor converter which uses an MvxColor.
However, the local:MvxBind="Background... property wants a drawable, hence my use of binding a ColorDrawable.
You need to look into the Value converters of MvvmCross: https://github.com/MvvmCross/MvvmCross/wiki/Value-Converters#the-mvx-color-valueconverters
Using those you can set a color like this:
local:MvxBind="BackgroundColor NativeColor(CurrentColor)"
Or for iOS:
set.Bind(field)
.For(field => field.BackgroundColor)
.To(vm => vm.CurrentColor)
.WithConversion("NativeColor");
And Windows:
Fill="{Binding CurrentColor, Converter={StaticResource NativeColor}}"
I just defined a subclass of my cardview with a own property for it, since you cant directly access it else it seems and i didnt find a binding for it yet?
public class BindableColorCardView: CardView
{
private Color m_cCardViewColor;
public Color CardViewColor
{
get { return m_cCardViewColor; }
set
{
m_cCardViewColor = value;
SetCardBackgroundColor(m_cCardViewColor);
}
}
And then just used binded property with a ValueConverter.
I know this was asked a very long time ago but maybe it will help somebody out.
The answer now is that you need to use CardViewBackgroundColor instead of CardBackgroundColor.
So in your case that would be:
cardview:MvxBind="CardViewBackgroundColor EventEntryBackgroundColour"
You should bind it to a Android.Graphics.Color though and not an integer.
To do that, you can create a converter class like below:
public class BackgroundColorConverter : MvxValueConverter<bool, Color>
{
protected override Color Convert(bool value, Type targetType, object parameter, CultureInfo culture)
{
return value ? Color.Black : Color.White;
}
}
You can call this converter from the xaml too and pass a boolean from your ViewModel to display the correct color. Example:
cardview:MvxBind="CardViewBackgroundColor BackgroundColor(Selected)"
I want to set an image's source according to its DataContext in a ChildWindow. Here is the XAML file:
<controls:ChildWindow x:Class="CEM.Controls.DialogWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" Title="{Binding Title}">
...
<Image x:Name="DialogIcon"></Image>
...
</controls:ChildWindow>
It's working fine if I override the Show method of the ChildWindow and set the image's source:
public new void Show()
{
DialogIcon.Source = new BitmapImage(new Uri(#"/Images/DialogWindow/Confirm.png", UriKind.Relative));
base.Show();
}
But it looks ugly and it's not the "silverlight way", so I decide to change:
<Image x:Name="DialogIcon" Source="{Binding DialogIconType, Converter={StaticResource DialogIconConverter}}"></Image>
You see I have a DialogIconConverter registered to bind the source from the DataContext.
public class DialogIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//currently it's an hard-coded path
return new BitmapImage(new Uri(#"/Images/DialogWindow/Confirm.png", UriKind.Relative));
}
...
}
But it's not working now, I have several other converters in this control which are working fine. Only this one is not working. Can you help to find where the problem is?
EDIT: DialogIconType is an enum, and also it's a property of DialogContext. An instance of DialogContext will be assigned to DataContext property of the DialogWindow.
public enum DialogIconType
{
Confirm,
Alert,
Error
}
public class DialogContext
{
public string Title { get; set; }
public string Content { get; set; }
public DialogButtons Buttons { get; set; }
public DialogIconType IconType { get; set; }
}
internal DialogWindow(DialogContext context)
{
InitializeComponent();
this.DataContext = context;
}
might be silly, but did you make sure that your converter is referenced properly in your xaml file ?
otherwise, I suggest trying this syntax as path for your URI (with images setup as resources):
return new BitmapImage(new Uri("pack://application:,,,/Images/DialogWindow/Confirm.png", UriKind.Relative));
EDIT :
ok, I think I've got it :
look into your output window, you will probably see some error 40 binding ... blablabla...
My guess is that the converter is right, but the source of the binding isn't, so basically the converter is not even used.
The reason is that your DialogIconType is not a dependency property, so it cannot be bound to.
in other words, this :
public DialogIconType IconType { get; set; }
should become this :
public static DependencyProperty IconTypeProperty = DependencyProperty.Register("IconType", typeof(DialogIconType), typeof(DialogContext));
public DialogIconType IconType
{
get { return (DialogIconType)(GetValue(IconTypeProperty)); }
set { SetValue(IconTypeProperty , value); }
}
plus, in your Xaml, you should Bind to "IconType", and not "DialogIconType" (which is a type and not a property)
(this might even be the sole issue, as I'm not sure if a dependencyProperty Is actually realy needed here, now that I think of it)
Assuming that DialogIconType is the path to your image (e.g. "Images/DialogWindow/Confirm.png"), it should work without a valueconverter as shown below:
<Image Source="{Binding DialogIconType}" />
EDIT:
Returning the path to the image from the valueconverter's Convert method is also possible - i.e.:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return "Images/DialogWindow/Confirm.png";
}
EDIT 2:
The following also works using UriKind.Relative:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new BitmapImage(new Uri("Images/DialogWindow/Confirm.png", UriKind.Relative));
}