[WPF] Почему не работает Binding в режиме OneWayToSource
От: Cynic Россия  
Дата: 11.03.19 16:30
Оценка:
В общем, написал такой я код:
    public class DataType : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        int _value;

        public string Name { get; set; }
        public int Value
        {
            get => _value;
            set
            {
                _value = value;
                Debug.WriteLine($"{Name} => {Value}");
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Value"));
            }
        }
    }

<Window x:Class="BindingToSubProperties.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BindingToSubProperties"
        mc:Ignorable="d"
        Title="BindingToSubProperties" WindowStartupLocation="CenterScreen" SizeToContent="WidthAndHeight">
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto" MinWidth="180"/>
        </Grid.ColumnDefinitions>

        <Label Content="Name"/>
        <ComboBox x:Name="cmbName" Grid.Column="1" Margin="3" DisplayMemberPath="Name">
            <local:DataType Name="Type1" Value="1"/>
            <local:DataType Name="Type2" Value="2"/>
            <local:DataType Name="Type3" Value="3"/>
        </ComboBox>

        <Label Content="Current value" Grid.Row="1"/>
        <TextBox Grid.Row="1" Grid.Column="1" Margin="3" Text="{Binding ElementName=cmbName, Path=SelectedValue.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" IsEnabled="False"/>

        <Label Content="New value" Grid.Row="2"/>
        <TextBox Grid.Row="2" Grid.Column="1" Margin="3" Text="{Binding ElementName=cmbName, Path=SelectedValue.Value, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}"/>
    </Grid>
</Window>

Вопрос такой: Почему привязка к cmbName работает в режиме TwoWay, но ни в какую не работает в режиме OneWayToSource?
:)
Re: [WPF] Почему не работает Binding в режиме OneWayToSource
От: bkat  
Дата: 11.03.19 19:52
Оценка:
Здравствуйте, Cynic, Вы писали:


C>Вопрос такой: Почему привязка к cmbName работает в режиме TwoWay, но ни в какую не работает в режиме OneWayToSource?


Тебе нужен OneWay, а не OneWayToSource.
Вот первая же ссылка в гугле:
https://stackoverflow.com/questions/2305179/what-are-the-various-wpf-binding-modes
Re: [WPF] Почему не работает Binding в режиме OneWayToSource
От: Cynic Россия  
Дата: 11.03.19 20:01
Оценка:
Вот такой workaround придумал, чтобы заставить вести себя TwoWay Binding как OneWayToSource Binding:
1) Создаем конвертер который "умеет" конвертировать только в направлении Target->Source:
    [ValueConversion(typeof(string), typeof(int))]
    public class StringToIntOneWayConverter : MarkupExtension, IValueConverter
    {
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }

        // Source -> Target (int -> string)
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // Специально чтобы TextBox.Text сбрасывался в string.Empty
            return DependencyProperty.UnsetValue;
        }

        // Target -> Source (string -> int) 
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if(targetType == typeof(int))
            {
                var stringValue = value as string;
                if (string.IsNullOrEmpty(stringValue))
                    return Binding.DoNothing;
                if (int.TryParse(stringValue, out int intValue))
                    return intValue;
            }

            return DependencyProperty.UnsetValue;
        }
    }

2) Изменяем тип привзяки для второго TextBox'а (там где NewValue) с OneWayToSource на TwoWay и прицепляем к привязке конвертер:
...
 <TextBox Grid.Row="2" Grid.Column="1" Margin="3" Text="{Binding ElementName=cmbName, Path=SelectedValue.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, Converter={local:StringToIntOneWayConverter}}"/>
...

И всё работает Вот только вопрос по прежнему висит
:)
Re[2]: [WPF] Почему не работает Binding в режиме OneWayToSource
От: Cynic Россия  
Дата: 11.03.19 20:13
Оценка:
Здравствуйте, bkat, Вы писали:

B>Тебе нужен OneWay, а не OneWayToSource.


Нет, мне нужно чтобы изменения в TextBox.Text обновляли DataType.Value, но не наоборот. Поскольку TextBox.Text это Target, то мне нужно чтобы привязка работала только в направлении Target -> Source, и этот режим называется OneWayToSource. Цитирую вашу же ссылку:

OneWayToSource: This is the opposite of OneWay -- user interface value changes update the bound property.

:)
Re[3]: [WPF] Почему не работает Binding в режиме OneWayToSou
От: bkat  
Дата: 11.03.19 21:26
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Здравствуйте, bkat, Вы писали:


B>>Тебе нужен OneWay, а не OneWayToSource.


C>Нет, мне нужно чтобы изменения в TextBox.Text обновляли DataType.Value, но не наоборот. Поскольку TextBox.Text это Target, то мне нужно чтобы привязка работала только в направлении Target -> Source, и этот режим называется OneWayToSource. Цитирую вашу же ссылку:

C>

C>OneWayToSource: This is the opposite of OneWay -- user interface value changes update the bound property.




Ааа. теперь понял что ты хочешь.

Там проблема не в OneWayToSource, а в том, что байндишь на SelectedValue.Value.
Попробуй поставить SelectedIndex изначально и поиграйся. Тогда будет понятно.
        <ComboBox
            x:Name="cmbName"
            Grid.Column="1"
            Margin="3"
            SelectedIndex="0"
            DisplayMemberPath="Name">
            <local:DataType Name="Type1" Value="1" />
            <local:DataType Name="Type2" Value="2" />
            <local:DataType Name="Type3" Value="3" />
        </ComboBox>


А если ты стартуешь с невыбранным элепментов в комбобоксе, то у тебя SelectedValue.Value в самом начале неопределен и остается неопределнным,
посколько ты выбрал OneWayToSource.
А если укажешь Mode=TwoWay, то и без конвертера будет работать.
С конвертером у тебя "вдруг" заработало, потому что там у тебя Mode=TwoWay.

А вообще OneWayToSource больше используют в MVVM, где у тебя в модели есть куда нормально байндить.
А так между элементами view надо точно понимать что и когда существует.

Вот к примеру можешь попробовать такое:
        <TextBox
            Grid.Row="2"
            Grid.Column="1"
            Margin="3"
            Text="{Binding ElementName=TextBox_Read, Path=Text, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />

        <TextBox
            x:Name="TextBox_Read"
            Grid.Row="3"
            Grid.Column="1"
            Margin="3"
            IsEnabled="False" />

Все работает. Текст копируется с одного TextBox в другой.
Отредактировано 11.03.2019 21:28 bkat . Предыдущая версия .
Re[4]: [WPF] Почему не работает Binding в режиме OneWayToSou
От: Cynic Россия  
Дата: 12.03.19 10:25
Оценка:
Здравствуйте, bkat, Вы писали:

B>Попробуй поставить SelectedIndex изначально и поиграйся.

B>А если ты стартуешь с невыбранным элепментов в комбобоксе, то у тебя SelectedValue.Value в самом начале неопределен и остается неопределнным, посколько ты выбрал OneWayToSource.

Ну даже если установить SelectedIndex Binding всё равно не сработает, хотя должен.

B>А если укажешь Mode=TwoWay, то и без конвертера будет работать.


Потому что дефолтный конвертер умеет вызывать ToString для стандартных типов данных, т.е. в принципе не нужен конвертер когда тебе надо int в String преобразовать. А я конвертер сделал, чтобы TwoWay Binding "переделать" в OneWayToSource Binding

B>С конвертером у тебя "вдруг" заработало, потому что там у тебя Mode=TwoWay.


У меня ни чего "вдруг" не работает, я целенаправленно искал решение проблемы и нашел.

B>Там проблема не в OneWayToSource, а в том, что байндишь на SelectedValue.Value.


Это-то понятно. Если например привязать тот-же TextBox к ComboBox.SelectedIndex, то будет работать как TwoWay, так и OneWayToSource.
:)
Re[5]: [WPF] Почему не работает Binding в режиме OneWayToSou
От: ksg71 Германия  
Дата: 12.03.19 13:54
Оценка: 1 (1) +1
Здравствуйте, Cynic, Вы писали:

C>Здравствуйте, bkat, Вы писали:



C>Это-то понятно. Если например привязать тот-же TextBox к ComboBox.SelectedIndex, то будет работать как TwoWay, так и OneWayToSource.


TwoWay слушает события с обоих сторон, соответственно когда SelectedValue в комбобоксе изменилось то биндинг об этом узнал, обновил ссылку на объект и начал проталкивать
изменения в текущее SelectedValue комбобокса.
при OneWayToSource биндинг не слушает изменений в комбобоксе, поэтому он запомнил null при открытии формы и не обновляет эту ссылку, даже когда SelectedValue в комбобоксе изменилось
Das Reich der Freiheit beginnt da, wo die Arbeit aufhört. (c) Karl Marx
Отредактировано 12.03.2019 14:28 ksg71 . Предыдущая версия .
Re[5]: [WPF] Почему не работает Binding в режиме OneWayToSou
От: bkat  
Дата: 12.03.19 17:08
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Здравствуйте, bkat, Вы писали:


B>>Попробуй поставить SelectedIndex изначально и поиграйся.

B>>А если ты стартуешь с невыбранным элепментов в комбобоксе, то у тебя SelectedValue.Value в самом начале неопределен и остается неопределнным, посколько ты выбрал OneWayToSource.

C>Ну даже если установить SelectedIndex Binding всё равно не сработает, хотя должен.


Так что ли?
<TextBox Grid.Row="1" Grid.Column="1" Margin="3" Text="{Binding ElementName=cmbName, Path=SelectedIndex, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}" IsEnabled="False"/>


Должно работать. Будет выбираться другой элемент комбобокса в зависимости от того, что ты введешь в TextBox.

Ну да ладно... Ситуация вроде прояснилась, а в реальном коде у тебя наверняка все немного сложнее.
Валидаторы там всякие, значения по-умолчанию и структуры данных сложнее.

А так вот врямо байндить на SelectedValue.Value я бы не стал.
intellisense не работает. Рефекторинг тоже рискованный если ты вдруг захочешь переименовать что.
Я бы завел SelectedItem в view model и плясал бы от него.
Отредактировано 12.03.2019 17:31 bkat . Предыдущая версия .
Re[6]: [WPF] Почему не работает Binding в режиме OneWayToSou
От: Cynic Россия  
Дата: 12.03.19 18:32
Оценка:
Здравствуйте, bkat, Вы писали:

B>Так что ли?

B>
B><TextBox Grid.Row="1" Grid.Column="1" Margin="3" Text="{Binding ElementName=cmbName, Path=SelectedIndex, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}" IsEnabled="False"/>
B>


B>Должно работать. Будет выбираться другой элемент комбобокса в зависимости от того, что ты введешь в TextBox.


Должно но не работает, я сегодня проверял. С TwoWay работает, с OneWayToSource нет.

B>Ну да ладно... Ситуация вроде прояснилась, а в реальном коде у тебя наверняка все немного сложнее.

B>Валидаторы там всякие, значения по-умолчанию и структуры данных сложнее.

Проблема в том, что не работает ни как, ни в учебном примере, ни в сложном.
:)
Re[6]: [WPF] Почему не работает Binding в режиме OneWayToSou
От: Cynic Россия  
Дата: 12.03.19 18:53
Оценка:
Здравствуйте, ksg71, Вы писали:

K>TwoWay слушает события с обоих сторон, соответственно когда SelectedValue в комбобоксе изменилось то биндинг об этом узнал, обновил ссылку на объект и начал проталкивать

K>изменения в текущее SelectedValue комбобокса.
K>при OneWayToSource биндинг не слушает изменений в комбобоксе, поэтому он запомнил null при открытии формы и не обновляет эту ссылку, даже когда SelectedValue в комбобоксе изменилось

А это ваше предположение или есть какой то официальный документ который это описывает?
:)
Re[7]: [WPF] Почему не работает Binding в режиме OneWayToSou
От: bkat  
Дата: 12.03.19 20:03
Оценка: 1 (1)
Здравствуйте, Cynic, Вы писали:

C>Здравствуйте, bkat, Вы писали:


B>>Должно работать. Будет выбираться другой элемент комбобокса в зависимости от того, что ты введешь в TextBox.


C>Должно но не работает, я сегодня проверял. С TwoWay работает, с OneWayToSource нет.


Не знаю, как оно там у тебя.
Специально не поленился.
Вот такой хaml работает.
Как только в TextBox_ComboBoxIndexSelectorнабираешь 0, в комбобоксе выбирается первый элемент и т.д...

<Window
    x:Class="WpfApp2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WpfApp2"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" MinWidth="180" />
        </Grid.ColumnDefinitions>

        <Label Content="Name" />
        <ComboBox
            x:Name="cmbName"
            Grid.Column="1"
            Margin="3"
            DisplayMemberPath="Name">
            <local:DataType Name="Type1" Value="1" />
            <local:DataType Name="Type2" Value="2" />
            <local:DataType Name="Type3" Value="3" />
        </ComboBox>

        <Label Grid.Row="1" Content="Current value" />
        <TextBox
            Grid.Row="1"
            Grid.Column="1"
            Margin="3"
            IsEnabled="False"
            Text="{Binding ElementName=cmbName, Path=SelectedValue.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />

        <Label Grid.Row="2" Content="New value" />
        <TextBox
            x:Name="TextBox_ComboBoxIndexSelector"
            Grid.Row="2"
            Grid.Column="1"
            Margin="3"
            Text="{Binding ElementName=cmbName, Path=SelectedIndex, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}" />
    </Grid>
</Window>


Так что ты что-то просмотрел...
Re[7]: [WPF] Почему не работает Binding в режиме OneWayToSou
От: bkat  
Дата: 12.03.19 20:07
Оценка:
Здравствуйте, Cynic, Вы писали:

C>Здравствуйте, ksg71, Вы писали:


K>>TwoWay слушает события с обоих сторон, соответственно когда SelectedValue в комбобоксе изменилось то биндинг об этом узнал, обновил ссылку на объект и начал проталкивать

K>>изменения в текущее SelectedValue комбобокса.
K>>при OneWayToSource биндинг не слушает изменений в комбобоксе, поэтому он запомнил null при открытии формы и не обновляет эту ссылку, даже когда SelectedValue в комбобоксе изменилось

C>А это ваше предположение или есть какой то официальный документ который это описывает?


Если тебя это на самом деле интересует, то ты даже в исходники WPF можешь глянуть.
Все работает как описал ksg71.

Ну или проще...
В дебагере посмотри на BindingExpression в твоем оригинальном text box и там все сразу понятно.
Re[8]: [WPF] Почему не работает Binding в режиме OneWayToSou
От: Cynic Россия  
Дата: 13.03.19 21:22
Оценка:
Здравствуйте, bkat, Вы писали:

B>Так что ты что-то просмотрел...


Согласен, это я прогнал. В этом случае, всё работает.
:)
Re[6]: [WPF] Почему не работает Binding в режиме OneWayToSou
От: Cynic Россия  
Дата: 13.03.19 22:21
Оценка:
Здравствуйте, ksg71, Вы писали:

K>TwoWay слушает события с обоих сторон, соответственно когда SelectedValue в комбобоксе изменилось то биндинг об этом узнал, обновил ссылку на объект и начал проталкивать

K>изменения в текущее SelectedValue комбобокса.
K>при OneWayToSource биндинг не слушает изменений в комбобоксе, поэтому он запомнил null при открытии формы и не обновляет эту ссылку, даже когда SelectedValue в комбобоксе изменилось

В общем я тут вспомнил как дебажить Binding'и всё прояснилось:
// При загрузке формы:
System.Windows.Data Information: 41 : BindingExpression path error: 'Value' property not found for 'object' because data item is null.  This could happen because the data provider has not produced any data yet. BindingExpression:Path=SelectedValue.Value; DataItem='ComboBox' (Name='cmbName'); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=SelectedValue.Value; DataItem='ComboBox' (Name='cmbName'); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 21 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=SelectedValue.Value; DataItem='ComboBox' (Name='cmbName'); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=SelectedValue.Value; DataItem='ComboBox' (Name='cmbName'); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
System.Windows.Data Warning: 56 : Created BindingExpression (hash=65445301) for Binding (hash=38376892)
System.Windows.Data Warning: 58 :   Path: 'SelectedValue.Value'
System.Windows.Data Warning: 62 : BindingExpression (hash=65445301): Attach to System.Windows.Controls.TextBox.Text (hash=34090260)
System.Windows.Data Warning: 67 : BindingExpression (hash=65445301): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=65445301): Found data context element: <null> (OK)
System.Windows.Data Warning: 74 :     Lookup name cmbName:  queried TextBox (hash=34090260)
System.Windows.Data Warning: 78 : BindingExpression (hash=65445301): Activate with root item ComboBox (hash=4152081)
System.Windows.Data Warning: 107 : BindingExpression (hash=65445301):   At level 0 using cached accessor for ComboBox.SelectedValue: DependencyProperty(SelectedValue)
System.Windows.Data Warning: 104 : BindingExpression (hash=65445301): Replace item at level 0 with ComboBox (hash=4152081), using accessor DependencyProperty(SelectedValue)
System.Windows.Data Warning: 101 : BindingExpression (hash=65445301): GetValue at level 0 from ComboBox (hash=4152081) using DependencyProperty(SelectedValue): <null>
System.Windows.Data Warning: 106 : BindingExpression (hash=65445301):   Item at level 1 is null - no accessor
System.Windows.Data Information: 41 : BindingExpression path error: 'Value' property not found for 'object' because data item is null.  This could happen because the data provider has not produced any data yet. BindingExpression:Path=SelectedValue.Value; DataItem='ComboBox' (Name='cmbName'); target element is 'TextBox' (Name='txtNewValue'); target property is 'Text' (type 'String')
System.Windows.Data Warning: 90 : BindingExpression (hash=65445301): Update - got raw value ''
System.Windows.Data Warning: 94 : BindingExpression (hash=65445301): Update - using final value ''

// При выборе любого элемента в ComboBox:
System.Windows.Data Information: 41 : BindingExpression path error: 'Name' property not found for 'object' because data item is null.  This could happen because the data provider has not produced any data yet. BindingExpression:Path=Name; DataItem=null; target element is 'ComboBox' (Name='cmbName'); target property is 'NoTarget' (type 'Object')
System.Windows.Data Information: 20 : BindingExpression cannot retrieve value due to missing information. BindingExpression:Path=Name; DataItem=null; target element is 'ComboBox' (Name='cmbName'); target property is 'NoTarget' (type 'Object')
System.Windows.Data Information: 21 : BindingExpression cannot retrieve value from null data item. This could happen when binding is detached or when binding to a Nullable type that has no value. BindingExpression:Path=Name; DataItem=null; target element is 'ComboBox' (Name='cmbName'); target property is 'NoTarget' (type 'Object')
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Name; DataItem=null; target element is 'ComboBox' (Name='cmbName'); target property is 'NoTarget' (type 'Object')

Получается если нужно, чтобы можно было выбрав объект в cmbName изменять значение свойства SelectedValue.Value только в направлении OneWayToSource, то единственный способ это сделать только с помощью Binding'ов (не городя свойств в форме и т.п.) это:
  1. Включить режим привязки TwoWay, чтобы привязка следила за изменениями как в Source, так и в Target. OneWayToSource работать не будет, т.к. отслеживает изменения только в Target
  2. Сделать конвертер который будет пропускать значения только в направления Target -> Source
:)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.