C # & wpf - Неожиданное поведение цепочки (OneWay-Mode) между ListBox-Label-ComboBox - PullRequest
0 голосов
/ 09 января 2019

У меня следующая странная (для меня) ситуация
ListBox привязан (как источник) к метке в режиме OneWay, т.е. ListBox доступен только для чтения. Метка затем привязывается к ComboBox с привязкой TwoWay

ListBox --> Label <--> ComboBox - arrows denote binding mode

Странно, что при запуске программы и выборе пользователем списка в ListBox все 3 элемента управления работают так, как и ожидалось. Но как только в Combobox выбран один индекс, Label продолжает работать должным образом (обновляется Combo), но привязка OneWay к ListBox исчезает (имеет значение null) и ListBox больше не может обновлять Label.

Мне кажется, что когда содержимое метки устанавливается другими способами, кроме привязки OneWay (как здесь, с обновлением Combo или, возможно, с помощью ValueConverter), эта привязка очищается WPF.

Другое странное поведение состоит в том, что если эта привязка OneWay между ListBox и Label превращается в двустороннюю, то все работает отлично.

Вопрос что я делаю неправильно или, если это нормальное поведение, где я могу найти соответствующую документацию.

Ниже приведен упрощенный код и XAML, демонстрирующий случай. Мой обходной путь - установить содержимое метки с помощью кода в ListBox_SelectionChanged.

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Test_Chained_controls
{
   public partial class MainWindow : Window
   {
      public class ComboItems
      {
         public int iDX { get; set; }
         public string sDesc { get; set; }

         public ComboItems(int a, string b)
         {
            iDX = a;
            sDesc = b;
         }
      }

      public class ListItems
      {
         public int iLDX { get; set; }
         public ListItems(int a)
         {
            iLDX = a;
         }
      }

      public List<ListItems> intList = new List<ListItems>();
      public List<ComboItems> idx_StrList = new List<ComboItems>();

      public MainWindow()
      {
         InitializeComponent();

         intList.Add(new ListItems(0));
         intList.Add(new ListItems(1));
         intList.Add(new ListItems(2));
         intList.Add(new ListItems(3));

         idx_StrList.Add(new ComboItems(0, "Zero"));
         idx_StrList.Add(new ComboItems(1, "One"));
         idx_StrList.Add(new ComboItems(2, "Two"));
         idx_StrList.Add(new ComboItems(3, "Three"));
      }

      private void Window_Loaded(object sender, RoutedEventArgs e)
      {
         listBox.ItemsSource = intList;
         comboBox.ItemsSource = idx_StrList;
      }

      private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
      {
         //// Set Label Content in case of OneWay
         // var binding = BindingOperations.GetBinding(label, Label.ContentProperty);
         // if (binding != null)
         // {
         //    if (binding.Mode == BindingMode.OneWay)
         //       {}  // Binding set - do nothing
         // }
         // else label.Content = listBox.SelectedItem;
      }
   }
}

1018 * XAML *

<Window ... normal stuff
        xmlns:local="clr-namespace:Test_Chained_controls"
        mc:Ignorable="d"
        Title="MainWindow" Height="182" Width="500" Loaded="Window_Loaded">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="120"/>
            <ColumnDefinition Width="140"/>
        </Grid.ColumnDefinitions>

        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>
            <RowDefinition Height="100"/>
        </Grid.RowDefinitions>

        <Label Content="ListBox"    Grid.Row="0" Grid.Column="0" Margin="20,10,0,0" />
        <Label Content="Label"      Grid.Row="0" Grid.Column="1" Margin="20,10,0,0" />
        <Label Content="ComboBox"   Grid.Row="0" Grid.Column="2" Margin="20,10,0,0" />

        <ListBox x:Name="listBox"   Grid.Row="1" Grid.Column="0" Margin="0"  
                 DisplayMemberPath="iLDX" 
                 SelectedIndex="0"
                 IsSynchronizedWithCurrentItem="True" 
                 SelectionChanged="ListBox_SelectionChanged"/>

        <Border BorderThickness="1" Grid.Row="1" Grid.Column="1" Height="30" 
                Margin="20,20,0,0" BorderBrush="#FFACACAC"  >

            <!-- *********** Label with Mode=OneWay or TwoWay *********** -->
            <Label x:Name="label" Width="100" Height="25"
                   Content="{Binding ElementName=listBox, 
                             Path=SelectedItem.iLDX, Mode=OneWay }" />
        </Border>

        <ComboBox x:Name="comboBox" Grid.Row="1" Grid.Column="2" 
                                   Height="30" Margin="20,20,0,0"  

                  DisplayMemberPath="sDesc" 
                  SelectedValue="{Binding ElementName=label, Path=Content, 
                  TargetNullValue=0, FallbackValue=0, Mode=TwoWay}"
                  SelectedValuePath="iDX"  />
    </Grid>
</Window>

РЕДАКТИРОВАТЬ

Соответствующая документация: Обзор свойств зависимостей

Локальное значение: Локальное значение может быть установлено с помощью удобной оболочки свойств, которая также приравнивается к установке в качестве атрибута или элемента свойства в XAML, или посредством вызова SetValue метод с использованием свойства конкретного экземпляра. Если вы устанавливаете локальное значение с помощью привязки или статического ресурса, каждый из них действует в приоритете, как если бы было установлено локальное значение, и привязки или ссылки на ресурсы стираются, если задано новое локальное значение.

и далее вниз

Если вы установите другое локальное значение для свойства, которое изначально содержало значение Binding, вы перезапишете привязку полностью, а не только значение времени выполнения привязки.

Как я понимаю, с этим случаем произошла какая-то ошибка, исправленная введением DependencyObject. SetCurrentValue Исправление локальных значений Control Control Solution

public void SetCurrentValue (System.Windows.DependencyProperty dp, object value);
// Sets the value of a dependency property without changing its value source.

Мне кажется, что привязка Combobox TwoWay все еще использует SetValue , и поэтому привязка для (label) стирается при использовании my (combobox).

Чтобы преодолеть это, я изменил привязку TwoWay (comboBox) на OneWay и ввел следующее в событие comboBox_DropDownClosed (показывающий выбранный в данный момент элемент), чтобы обновить (пометить) по коду без стирания существующей привязки

  private void comboBox_DropDownClosed(object sender, System.EventArgs e)
  {
     Binding binding = BindingOperations.GetBinding(label, Label.ContentProperty);
     if (binding != null)
     {
        ComboItems ComboItem = comboBox.SelectedItem as ComboItems;
        int iDX = ComboItem.iDX;

        // Set label value without affecting existing binding
        label.SetCurrentValue(Label.ContentProperty, iDX);
     }
  }

При использовании SetCurrentValue код теперь работает так, как первоначально предполагалось, "имитируя" режим TwoWay.

1 Ответ

0 голосов
/ 10 января 2019

В этом нет ничего странного. Привязка данных предназначена для работы таким образом. Когда вы присваиваете привязку свойству зависимости, это означает, что вы меняете локальное значение этого свойства зависимости на выражение привязки. И любое обновление, предоставляемое источником привязки, будет эффективным значением этого свойства зависимости. Если привязка работает в одностороннем режиме, любое обновление этого свойства зависимости из другого источника, кроме источника привязки, перезапишет локальное значение, что приведет к потере привязки. С другой стороны, поскольку в двух режимах предполагается обновить источник привязки, объект зависимости будет считать любое значение без выражения как эффективное значение, привязка будет работать до тех пор, пока вы не замените или не очистите его.

  • DependencyObject.GetValue получает эффективное значение.
  • DependencyObject.ReadLocalValue получает локальное значение.
  • DependencyObject.SetValue устанавливает локальное значение.
  • DependencyObject.SetCurrentValue устанавливает эффективное значение.
  • DependencyObject.ClearValue очищает локальное значение.
...