Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SelectedIndex with OneWayToSource binding does not trigger

I have a problem with a particular xaml databinding. I have two listboxes (master-details, so the listboxes have IsSynchronizedWithCurrentItem set to true). I want my viewmodel to know when the selected item on the details listbox changes: I created an int property on my viewmodel class (i.e. we can call this property SelInd)and on the details viewmodel I bind this way:

    SelectedIndex="{Binding Mode=OneWayToSource, Path=SelInd}"

I get no errors/exceptions at runtime, but the binding does not trigger: my viewmodel's property does not get updated when the selected item changes. If I change the binding mode to TwoWay everything works fine, but that's not what I need. I need it to work with OneWayToSource (btw the same non-working behaviour applies if I bind SelectedItem to SelectedValue properties).

Why do those bindings do not trigger with OneWayToSource?

Here's a more complete code example, just to get the things clearer: EDIT: I can't show the real code (NDA) but I'll show here something simpler and similar enough (the Page's DataContext is an instance of the PageViewModel class explained later) I just need that my viewmodel class's SelInd property should always reflect the value of SelectedIndex in the second ListBox. I have found alternative methods for doing this (Event handler in code-behind or an Attached Behaviour) but right now I'm just curious about WHY it doesn't work with OneWayToSource binding.

<Page>
    <ContentControl x:Name="MainDataContext">
        <Grid DataContext={Binding Path=Masters}>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />

            </Grid.ColumnDefinitions>

            <ListBox Grid.Column="0"
                SelectionMode="Single"                           
             IsSynchronizedWithCurrentItem="True"
             ItemsSource="{Binding }">
                <ListBox.ItemContainerStyle>
            ...
            </ListBox.ItemContainerStyle>
            <ListBox.ItemTemplate>
                <DataTemplate>
                ....
                    </DataTemplate>
            </ListBox.ItemTemplate>
            </ListBox>

            <ListBox Grid.Column="1"
                SelectionMode="Single"                           
              SelectedIndex="{Binding Mode=OneWayToSource,  ElementName=MainDataContext,Path=DataContext.SelInd}"
             IsSynchronizedWithCurrentItem="True"
             ItemsSource="{Binding Path=Details}">
                <ListBox.ItemContainerStyle>
            ...
            </ListBox.ItemContainerStyle>
            <ListBox.ItemTemplate>
                <DataTemplate>
                ....
                    </DataTemplate>
            </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </ContentControl>
</Page>

Here's a sketch of the view model class

public class PageViewModel{
    public ObservableCollection<MasterClass> Masters {get;set;}

    public int SelInd {get;set;}

    ....
}

And here's MasterClass, it just holds a name and a list of details

public class MasterClass{
    public ObservableCollection<DetailsClass> Details {get;set;}

    public String MasterName {get;set;}

    ....
}
like image 716
Gufino2 Avatar asked Aug 07 '13 22:08

Gufino2


2 Answers

I think in your case, you must use the mode OneWay. By default, you have used mode TwoWay.

Quote from MSDN about TwoWay:

TwoWay binding causes changes to either the source property or the target property to automatically update the other. This type of binding is appropriate for editable forms or other fully-interactive UI scenarios. Most properties default to OneWay binding, but some dependency properties (typically properties of user-editable controls such as the Text property of TextBox and the IsChecked property of CheckBox) default to TwoWay binding. A programmatic way to determine whether a dependency property binds one-way or two-way by default is to get the property metadata of the property using GetMetadata and then check the Boolean value of the BindsTwoWayByDefault property.

Mode OneWay, that you need:

OneWay binding causes changes to the source property to automatically update the target property, but changes to the target property are not propagated back to the source property. This type of binding is appropriate if the control being bound is implicitly read-only. For instance, you may bind to a source such as a stock ticker or perhaps your target property has no control interface provided for making changes, such as a data-bound background color of a table. If there is no need to monitor the changes of the target property, using the OneWay binding mode avoids the overhead of the TwoWay binding mode.

Mode OneWayToSource:

OneWayToSource is the reverse of OneWay binding; it updates the source property when the target property changes. One example scenario is if you only need to re-evaluate the source value from the UI.

Below is a diagram for a better understanding of the:

enter image description here

Okay, then I'll show you an example that works for me. Perhaps it will be useful to you.

XAML

<Window x:Class="SelectedIndexHelp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:SelectedIndexHelp"
    Title="MainWindow" Height="350" Width="525"
    ContentRendered="Window_ContentRendered"
    WindowStartupLocation="CenterScreen">

    <Window.Resources>
        <local:SelectedIndexClass x:Key="SelectedIndexClass" />
    </Window.Resources>

    <Grid DataContext="{StaticResource SelectedIndexClass}">
        <ListBox x:Name="MyListBox" 
                 BorderThickness="1" 
                 Width="200" Height="200"
                 BorderBrush="#CE5E48" 
                 DisplayMemberPath="Name" 
                 Background="AliceBlue" 
                 SelectedIndex="{Binding MySelectedIndex, Mode=OneWayToSource}" />

        <Label Name="SelectedIndex" VerticalAlignment="Top"
               Content="{Binding MySelectedIndex}"
               ContentStringFormat="SelectedIndex: {0}"
               Width="100" Height="30" Background="Lavender" />
    </Grid>
</Window>

Code behind

public partial class MainWindow : Window
{
    public class Person
    {
        public string Name
        {
            get;
            set;
        }

        public int Age
        {
            get;
            set;
        }            
    }

    private ObservableCollection<Person> DataForListBox = new ObservableCollection<Person>();

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Window_ContentRendered(object sender, EventArgs e)
    {
        DataForListBox.Add(new Person()
        {
            Name = "Sam",
            Age = 22,
        });

        DataForListBox.Add(new Person()
        {
            Name = "Nick",
            Age = 21,
        });

        DataForListBox.Add(new Person()
        {
            Name = "Cris",
            Age = 25,
        });

        DataForListBox.Add(new Person()
        {
            Name = "Josh",
            Age = 36,
        });

        DataForListBox.Add(new Person()
        {
            Name = "Max",
            Age = 32,
        });

        DataForListBox.Add(new Person()
        {
            Name = "John",
            Age = 40,
        });

        MyListBox.ItemsSource = DataForListBox;
        MyListBox.Focus();
    }
}

public class SelectedIndexClass 
{
    private int? mySelectedIndex = 0;

    public int? MySelectedIndex
    {
        get
        {
            return mySelectedIndex;
        }

        set
        {
            mySelectedIndex = value;
        }
    }
}

Output

enter image description here

In this example, there is a class of data - Person, these data for ListBox. And the class SelectedIndexClass (DataContext), which contains the property MySelectedIndex, which is a parameter of binding OneWayToSource.

Edit: I'm glad you figured out with the problem. I'll try to explain by their example, why are you not working with ElementName case.

So, let's say we have this code:

<ContentControl x:Name="MainDataContext">
    <Grid x:Name="MainGrid" DataContext="{StaticResource SelectedIndexClass}">
        <ListBox x:Name="MyListBox" 
                    BorderThickness="1" 
                    Width="200" Height="200"
                    BorderBrush="#CE5E48" 
                    DisplayMemberPath="Name" 
                    Background="AliceBlue" 
                    SelectedIndex="{Binding Path=DataContext.MySelectedIndex, Mode=OneWayToSource, ElementName=MainDataContext}" />

        <Label Name="SelectedIndex" VerticalAlignment="Top"
                Content="{Binding MySelectedIndex}"
                ContentStringFormat="SelectedIndex: {0}"
                Width="100" Height="30" Background="Lavender" />
    </Grid>
</ContentControl>

As you probably understand, it will not work.

DataContext set on a specific node of the visual tree, all items below (in the visual tree) inherit it. This means that the DataContext will be working since the Grid and below the visual tree. Therefore, the following code will work:

<ContentControl x:Name="MainDataContext">
    <Grid x:Name="MainGrid" DataContext="{StaticResource SelectedIndexClass}">
        <ListBox x:Name="MyListBox" 
                    BorderThickness="1" 
                    Width="200" Height="200"
                    BorderBrush="#CE5E48" 
                    DisplayMemberPath="Name" 
                    Background="AliceBlue" 
                    SelectedIndex="{Binding Path=DataContext.MySelectedIndex, Mode=OneWayToSource, ElementName=MainGrid}" />

        <Label Name="SelectedIndex" VerticalAlignment="Top"
                Content="{Binding MySelectedIndex}"
                ContentStringFormat="SelectedIndex: {0}"
                Width="100" Height="30" Background="Lavender" />
    </Grid>
</ContentControl>

And also, it will work if the name of the point MyListBox. Usually, when set the DataContext, the element name is passed.

like image 168
Anatoliy Nikolaev Avatar answered Nov 20 '22 15:11

Anatoliy Nikolaev


Well, I found a way to make it work. I just removed the data-context "indirection" so I don't have to use ElementName in my bindings, and it started working. The working xaml example is:

<Page>
    <ContentControl >
        <Grid >
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />

            </Grid.ColumnDefinitions>

            <ListBox Grid.Column="0"
                SelectionMode="Single"                           
             IsSynchronizedWithCurrentItem="True"
             ItemsSource="{Binding Masters }">
                <ListBox.ItemContainerStyle>
            ...
            </ListBox.ItemContainerStyle>
            <ListBox.ItemTemplate>
                <DataTemplate>
                ....
                    </DataTemplate>
            </ListBox.ItemTemplate>
            </ListBox>

            <ListBox Grid.Column="1"
                SelectionMode="Single"                           
              SelectedIndex="{Binding Mode=OneWayToSource,  Path=SelInd}"
             IsSynchronizedWithCurrentItem="True"
             ItemsSource="{Binding Path=Masters/Details}">
                <ListBox.ItemContainerStyle>
            ...
            </ListBox.ItemContainerStyle>
            <ListBox.ItemTemplate>
                <DataTemplate>
                ....
                    </DataTemplate>
            </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </ContentControl>
</Page>

Now, if someone knows exactly WHY the binding using ElementName does not work, I'd like to know it :)

like image 35
Gufino2 Avatar answered Nov 20 '22 16:11

Gufino2