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
Related
I have a binding from a toggle button hooked up in code behind.
I want to bind the isChecked state from one button to 4 video controls to toggle the mute function. I am using multibinding to bind the toggle button to 4 different controls. My problem is using breakpoints i can see everything is triggered right up to the property mute property on each object, but the property "value" parameter never gets updated. It stays at the default setting when the controls are instantiated.
So first i create the bindings in code behind
IMultiValueConverter converter = new EmptyMultiValueConverter();
MultiBinding myMultiBinding = new MultiBinding();
myMultiBinding.Converter = converter;
myMultiBinding.UpdateSourceTrigger = UpdateSourceTrigger.Default;
myMultiBinding.Mode = BindingMode.OneWayToSource;
myMultiBinding.NotifyOnSourceUpdated = true;
for (int i = 1; i < _maxNumberofPlayers; i++)
{
VideoPlayer player = new VideoPlayer()
{
Mute = false
};
myMultiBinding.Bindings.Add(new Binding("Mute") { Source = player
});
}
btnMuteToggle.SetBinding(SimpleButton.IsCheckedProperty, myMultiBinding);
This all seems to work because when i click on the button i can see in the multivalue converter the correct isChecked button status arrives at the breakpoints, at ConvertBack below i can confirm the value is the correct bool that reflects the toggle button state.
public class EmptyMultiValueConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object
parameter, System.Globalization.CultureInfo culture)
{
// gets from the object source
return (bool)values[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object
parameter, System.Globalization.CultureInfo culture)
{
return new Object[] {value,value,value,value};
}
#endregion
}
At this point i can confirm it hits the Mute property, and triggers the SET 4 times, but as i trace through it the value parameter remains at the previously set value, and does not update to reflect the value passed to it via ConvertBack
// mute property in the media player user control
public bool Mute
{
get { return _media.IsMuted; }
set
{
if (_media.IsMuted == value)
return;
else
{
_media.IsMuted = value;
NotifyPropertyChanged("Mute");
}
}
}
Can anyone help please.
Have been tearing my hair out for 3 days.
It seemed to me using multibinding is an efficient way to hook up 4 seperat controls players and bind them to one button click.
well I have tried several options again and none of them work except code in a click button event
Multibinding just does not work at all.
I can see in break points that the Mute property is being called 4 times for 4 controls but the value parameter is never updated to the new property value.
I also tried binding the one button to 4 different controls
foreach(Player player in lsPlayers)
{
btnMuteToggle.SetBinding(SimpleButton.IsCheckedProperty, new Binding("Mute")
{
Source = player,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
Mode = BindingMode.OneWayToSource
});
}
This just results in one player object mute property being called.
You cant bind more than one control to the same button.
It just does not work.
so I just called the 4 objects in a click event.
I believe that in this case you may want to bind those 4 properties to the mute property individually and make sure each is set Mode to TwoWay. But if MultiValueConverter supports it, I can't particularly argue with your approach.
It appears that you have not made a Dependency Property for Mute. this is vital to the WPF Binding Workflow and tends to scale easier than NotifyPropertyChanged as NotifyPropertyChanged can tend towards requiring you to manage more. A DependencyProperty will abstract away both sending and receiving the updates to your property and the Xaml editor support for it is better.
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/how-to-implement-a-dependency-property
I find WPF Binding to be nice in theory, but still requires a lot more leg work than it needs to. Have a function. I add it to all of my WPF viewmodels and controls.
/// <summary>
/// simplify wpf binding
/// </summary>
/// <param name="name"></param>
/// <param name="type"></param>
/// <param name="preferNull">Fair Warning. if you create an instance, you will have also created a singleton of this property for all instances of this class. </param>
/// <returns></returns>
private static DependencyProperty AddDp(string name, Type type, bool preferNull = true)
{
return DependencyProperty.Register(name, type, typeof(Setting),
new PropertyMetadata((!preferNull || type.IsValueType) ? Activator.CreateInstance(type) : null));
}
Just inherit from DependencyObject and you can make mute look more like this:
public static readonly DependencyProperty MuteDp = AddDp(nameof(Mute), typeof(bool));
public bool Mute
{
get => (bool)GetValue(MuteDp);
set { if(value != Mute) SetValue(MuteDp, value); }
}
But Be Warned!
Sometimes WPF Binding to a Dependency Property updates the Dependency Property's internal values without entering the setter of its corresponding property! This was the case on a MultiValueBinding I used.
meaning it may never touch the Mute::Set accessor. This can be pretty problematic, to counter this issue you can use the various callbacks available to a DependencyObject!
private static DependencyProperty AddDp(string name, Type type, bool preferNull = true)
{
var dp = DependencyProperty.Register(name, type, typeof(Setting),
new PropertyMetadata(
(!preferNull || type.IsValueType) ? Activator.CreateInstance(type) : null
,new PropertyChangedCallback((dobj, dpe)=>
{
//after property changed.
//forcing usage of set accessor
((MuteContainerObj)dobj).Value = ((strong text)dobj).Value;
//or forcibly use an update function
((MuteContainerObj)dobj).MuteUpdated();
}),new CoerceValueCallback((dobj, o)=> {
//before property changed
return o;
})), new ValidateValueCallback((o)=> {
//before property change events
return true;
}));
return dp;
}
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).
<!-- 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 using a DataGrid to display some shop stock information. Each item can belong to one type of stock.
The relevant entity ('StockEntity') has properties such as:
'ItemId', 'ItemType', 'Grocery', 'Reading', 'Bathroom'.
A couple of example rows in this table would be:
27, 'Grocery', 'Apple', null, null, null
127, 'Reading', null, 'Reading lamp', null, null
I have no control over the database/entity structure.
The DataGrid column is a custom column, containing (amongst others), a TextBox. The DataGrid is bound to an ObservableCollection of StockEntity objects. I want to bind the value of the TextBox to the relevant property. For example, if 'ItemType' = 'Grocery', the TextBox displays the 'Grocery' property. If I change the value in the textbox, it should get written back to the 'Grocery' property.
Here's what I have so far:
XAML:
<TextBox Grid.Column="0" Padding="5" VerticalAlignment="Center" Width="155">
<TextBox.Text>
<Binding Path="."
Converter="{StaticResource StockDataToTextConverter}"
Mode="TwoWay"
UpdateSourceTrigger="LostFocus">
</Binding>
</TextBox.Text>
</TextBox>
The converter is simple:
private StockEntity stock;
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
this.stock = value as StockEntity;
string text="";
if(this.stock!=null){
text = StockModel.GetStockData(this.stock);
}
return text;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
string info = value as string;
if(info!=null && this.stock!=null){
StockModel.SetStockData(ref this.stock, info);
}
return stock;
}
The StockModel.Get/SetStockData() methods simply use reflection to get/put the info back into the correct property. The Convert() method works fine: the TextBox displays the correct data. If I edit the value in the TextBox, it just reverts back to the old value. ConvertBack() isn't called.
I think the ConvertBack() method isn't getting called because of the Binding Path=".", but I can't think of another way around this.
I also don't know if I can 'save' the bound object in the converter the way I have. It's critical that the value of the TextBox gets written back to the same entity object, to preserve the database connection properties of the entity!
Many thanks.
What I really wanted was something like:
<Binding Path={Binding Path="ItemType"} />
This is impossible!
The solution was to wrap the StockEntity object in my own class, and expose a 'binding property', which decided on which stock property to get/set. Instead of having a collection of StockEntity objects, I now have an ObservableCollection of WrapperClass objects to bind the DataGrid to. The wrapper looks something like:
public class WrapperClass{
public WrapperClass(StockEntity se)
{
this._stock = se;
}
private StockEntity _stock;
public stock {
get { return _stock; }
set { _stock = value; }
}
public string BindingProperty {
get
{
// use reflection to return value
return StockModel.GetStockData(this._stock);
}
set
{
// use reflection to set value
StockModel.SetStockData(ref this._stock);
}
}
}
The XAML was then simply:
<Binding Path="StockWrapper.BindingProperty" />
Can an object created in the code (ie C#) be used for binding in the XAML?
For example:
public class MyForm
{
private MyComplexObject complexObject;
public MyForm()
{
InitializeComponent();
}
public OnButtonClick(object sender, RoutedEventArgs e)
{
complexObject = new MyComplexObject();
}
}
complexObject is not created till a button is clicked. But once that button is clicked I would like to have a text box that is bound to complexObject.ID start showing the Id.
I would like to do that in the XAML if that is possible.
Can this be done? If so, how?
Yes, this can be done, binding to a property that you update with the desired value. I'd suggest you look into the MVVM pattern (Model-View-ViewModel), which is really useful for structuring this nicely working with WPF. Check out this video for a nice overview:
MVVM video
Using MMVM you would create a class which would be the ViewModel class. This one would typically be set to the DataContext of the View. Having done so you could add dynamic references to the other properties of the class, e.g. binding your text field to some property holding the Id og the ComplexObject. If your ViewModel class had a property ComplexObject, which again had a property ID you'd simply bind to the object like this:
<TextBlock Text="{Binding ComplexObject.ID}" />
Having this you could trigger creation of your ComplexObject from mouse click, which you should ideally set up as a command binding. Also note that the ViewModel class (or whoever is holding the ComplexObject needs to notify the View when the object has been set. This can either be done by making the ComplexObject a DependencyProperty or by making the class holding the property implement the INotifyPropertyChanged interface - giving it the PropertyChanged function to trigger the changed event. I prefer the latter.
One possibility would be to have your XAML bind to a property on your code behind. The getter for that property would return complexObject.ID, if complexObject != null. Otherwise, it returns something "default", whether that's null or 0 or default(ID's type). Similarly, the setter for that property would assign value to complexObject.ID if complexObject is, again, not null.
public int ID
{
get
{
if (complexObject != null)
return complexObject.ID;
return 0; // or null or some appropriate default
}
set
{
if (complexObject != null)
complexObject.ID = value;
}
}