简体   繁体   中英

Refresh binding with converter bound to other property

I am currently building a WinRT app for Windows 10 and I am facing an issue I can't seem to find an answer for.

In my main page, I have a list of map markers bound to an ObservableCollection in my ViewModel. For each of these markers, i need to display a text that can either be Property1 or Property2 from my MapMarker class, based on the value of another property (let's call it PropertySelector) of my ViewModel.

The best solution I found is to make a struct that contains both Property1 and Property2 in the MapMarker class, bind it to the text field of the marker and use a Converter to choose which one to display.

Since you can't bind a property to a ConverterParameter, I implemented a DependencyProperty in the Converter to give it access to PropertySelector. The DP works fine, the property in the Converter gets updated, but the markers are never updated. I get that it's because I didn't trigger any event that actually told the marker to update, but I didn't manage to achieve it by adding a PropertyChanged("MarkerList") to the PropertySelector setter or by trying to refresh programmatically the bindings when I change the property with things like GetBinding(Text).UpdateSource() , that by the way seem to have a different implementation from WPF.

Am I doing this right ? What can I do to force the bindings to refresh ?

Here is my relevant code :

MainPage.xaml

<Page.Resources>
        <local:PropertySelectorConverter x:Key="propertySelectorConverter" 
                                   PropertySelector="{Binding PropertySelector}" />
</Page.Resources>

...

<Maps:MapControl>
    <Maps:MapItemsControl ItemsSource="{Binding MarkerList}">
        <Maps:MapItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Properties, Converter={StaticResource propertySelectorConverter}}" />
            </DataTemplate>
        </Maps:MapItemsControl.ItemTemplate>
    </Maps:MapItemsControl>
</Maps:MapControl>
<Button Text="Switch Data" Click="SwitchButton_Click" />

MainPage.xaml.cs

public void SwitchButton_Click(object sender, EventArgs e)
{
    viewModel.PropertySelector= !viewModel.PropertySelector
}

ViewModel.cs

class ViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Marker> markerList = new ObservableCollection<Marker>();
    public ObservableCollection<Marker> MarkerList
    {
        get { return markerList; }
        set { markerList = value; OnPropertyChanged("MarkerList"); }
    }

    private bool propertySelector = false;
    public bool PropertySelector
    {
        get { return propertySelector; }
        set { propertySelector = value; OnPropertyChanged("PropertySelector"); }
    }
}

Marker.cs

public class Marker
{
    public Tuple<double, double> Properties { get; set; } = Tuple.Create(10, 7);
}

Converter.cs

public class PropertySelectorConverter : DependencyObject, IValueConverter
{
    public bool PropertySelector
    {
        get { return (bool)GetValue(PropertySelectorProperty); }
        set { SetValue(PropertySelectorProperty, value); }
    }

    public static readonly DependencyProperty PropertySelectorProperty =
        DependencyProperty.Register("PropertySelector", typeof(bool), typeof(PropertySelectorConverter), new PropertyMetadata(null, CurrentItemChangedCallback));

    private static void CurrentItemChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
    {

    }

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var properties = (Tuple<double, double>)value;
        return PropertySelector ? properties.Item1 : properties.Item2;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

Thank you for your time.

Lacking a good, minimal , complete code example that clearly illustrates your question, it is difficult if not impossible to provide specific advice. But there are some general thoughts to share…

First, in my experience the ConverterParameter is more useful for static (ie compile-time) information to be provided to the converter. Eg when you have written a general-purpose converter that needs some specific data for a given binding, but where that data value is known at compile-time.

In your scenario, you actually have multiple input values for the converter that vary at run-time. For this scenario, IMHO it is more appropriate to use MultiBinding . This allows you to provide two or more binding sources, where WPF will recomputed the bound value if any one of those sources change. Unfortunately, this is a WPF feature and, like many very useful WPF features, has been omitted from the Windows Store/Winrt API.

However, you can construct a simple intermediary view model class to accomplish the same. For example:

class MultiBindingViewModel : DependencyObject
{
    public static readonly DependencyProperty PropertiesProperty = DependencyProperty.Register(
        "Properties", typeof(Tuple<double, double>), typeof(MultiBindingViewModel), new PropertyMetadata(null, OnPropertyChanged);
    public static readonly DependencyProperty PropertySelectorProperty = DependencyProperty.Register(
        "PropertySelector", typeof(bool), typeof(MultiBindingViewModel), new PropertyMetadata(null, OnPropertyChanged);
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
        "Value", typeof(double), typeof(MultiBindingViewModel), null);

    public Tuple<double, double> Properties
    {
        get { return (Tuple<double, double>)GetValue(PropertiesProperty); }
        set { SetValue(PropertiesProperty, value); }
    }

    public bool PropertySelector
    {
        get { return (bool)GetValue(PropertySelectorProperty); }
        set { SetValue(PropertySelectorProperty, value); }
    }

    public double Value
    {
        get { return (double)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        MultiBindingViewModel model = (MultiBindingViewModel)d;

        model.Value = model.PropertySelector ? model.Properties.Item1 : model.Properties.Item2;
    }
}

Then use that in your XAML, something like:

<TextBlock>
    <TextBlock.Text>
        <Binding Path="Value">
            <Binding.Source>
                <local:MultiBindingViewModel Properties="{Binding Properties}"
                                             PropertySelector="{Binding PropertySelector}/>
            </Binding.Source>
        </Binding>
    </TextBlock.Text>
</TextBlock>

Caveat: lacking a complete code example to start with, the above is just browser-written code. There may be syntax errors, or I might even have left out some key Windows Store-required code. For sure, the exact binding sources, paths, and XML namespace may need some tweaking, as I have no way to know for sure how you've set up your data contexts, etc.

But hopefully the above shows clearly enough the basic approach that you can use it in your project.


For completeness, here is what the WPF approach using MultiBinding would look like:

A MultiBinding will always have a converter, implementing IMultiValueConverter . The Convert() method of that interface looks a like that of IValueConverter , except that instead of the object value parameter allowing a single input value to be converted, it has the object[] values parameter.

Based on the code you provided, I would expect your converter to look like this:

public class PropertySelectorConverter : IMultiValueConverter
{    
    public object Convert(object[] values, Type targetType, object parameter, string language)
    {
        var properties = (Tuple<double, double>)values[0];
        bool propertySelector = (bool)values[1];

        return propertySelector ? properties.Item1 : properties.Item2;
    }

    public object ConvertBack(object[] values, Type targetType, object parameter, string language)
    {
        throw new NotSupportedException();
    }
}

Then in your XAML, you would do something like this:

<TextBlock>
    <TextBlock.Text>
        <MultiBinding Converter="{StaticResource propertySelectorConverter}">
            <Binding Source="." Path="Properties"/>
            <Binding Source="." Path="PropertySelector"/>
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM