Binding в WPF
От: Аноним  
Дата: 20.07.09 10:04
Оценка:
Суть проблемы: пишется составной контрол — Slider + TextBox, оба элемента привязаны к двухсторонней привязкой к DependencyProperty "Value" этого контрола. Т.е. дергаем слайдер — меняется текст, вводим текст — перемещается слайдер. Соответственно к Value извне также привязывается свойство объекта, над которым мы все эти действия и производим. Внутри самого контрола значение корректируется через CoerceValueCallBack (выход за границы, количество знаков после запятой и т.д.), но вот наружу по биндингу прилетает значение не скорректированное. Получается что биндинг происходит раньше установки самого свойства и, соответственно, до вызова CoerceValueCallback! Как с этим бороться?

20.07.09 15:37: Перенесено модератором из '.NET' — AndrewVK
Re: Binding в WPF
От: notacat  
Дата: 20.07.09 11:48
Оценка:
А>Суть проблемы: пишется составной контрол — Slider + TextBox, оба элемента привязаны к двухсторонней привязкой к DependencyProperty "Value" этого контрола. Т.е. дергаем слайдер — меняется текст, вводим текст — перемещается слайдер. Соответственно к Value извне также привязывается свойство объекта, над которым мы все эти действия и производим. Внутри самого контрола значение корректируется через CoerceValueCallBack (выход за границы, количество знаков после запятой и т.д.), но вот наружу по биндингу прилетает значение не скорректированное. Получается что биндинг происходит раньше установки самого свойства и, соответственно, до вызова CoerceValueCallback! Как с этим бороться?

Поищите в msdn "Dependency Property Value Precedence", почитайте, может понятнее будет.

А практически, когда в PropertyChangedCallback прилетает новое значение, там можно проверить, если значение свойства не скорректированное, то явно вызвать CoerceValue для этого свойства. Как-то так:

if (!DependencyPropertyHelper.GetValueSource(this, e.Property).IsCoerced)
{
    CoerceValue(e.Property);
    // return; 
}
Re[2]: Binding в WPF
От: Аноним  
Дата: 21.07.09 03:45
Оценка:
Здравствуйте, notacat, Вы писали:

А>>Суть проблемы: пишется составной контрол — Slider + TextBox, оба элемента привязаны к двухсторонней привязкой к DependencyProperty "Value" этого контрола. Т.е. дергаем слайдер — меняется текст, вводим текст — перемещается слайдер. Соответственно к Value извне также привязывается свойство объекта, над которым мы все эти действия и производим. Внутри самого контрола значение корректируется через CoerceValueCallBack (выход за границы, количество знаков после запятой и т.д.), но вот наружу по биндингу прилетает значение не скорректированное. Получается что биндинг происходит раньше установки самого свойства и, соответственно, до вызова CoerceValueCallback! Как с этим бороться?


N>Поищите в msdn "Dependency Property Value Precedence", почитайте, может понятнее будет.


N>А практически, когда в PropertyChangedCallback прилетает новое значение, там можно проверить, если значение свойства не скорректированное, то явно вызвать CoerceValue для этого свойства. Как-то так:


N>
N>if (!DependencyPropertyHelper.GetValueSource(this, e.Property).IsCoerced)
N>{
N>    CoerceValue(e.Property);
N>    // return; 
N>}
N>


MSDN я читал, решения и даже намека на причины такой проблемы там нет. Скорее всего это не проблема DependencyProperty, а проблема биндинга. Получается, что биндинг либо происходит в обход промежуточных свойств (т.е. сразу от конечного источника к конечной цели), либо передает базовое значение. В самом источнике проверять значения очень не хотелось бы, тем более таким изощренным способом, тем более что он является оберткой над нативным кодом и наследоваться от DependencyObject возможности нет.
Re[3]: Binding в WPF
От: MxKazan Португалия  
Дата: 21.07.09 07:28
Оценка:
Здравствуйте, Аноним, Вы писали:

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


А>>>Суть проблемы: пишется составной контрол — Slider + TextBox, оба элемента привязаны к двухсторонней привязкой к DependencyProperty "Value" этого контрола. Т.е. дергаем слайдер — меняется текст, вводим текст — перемещается слайдер. Соответственно к Value извне также привязывается свойство объекта, над которым мы все эти действия и производим. Внутри самого контрола значение корректируется через CoerceValueCallBack (выход за границы, количество знаков после запятой и т.д.), но вот наружу по биндингу прилетает значение не скорректированное. Получается что биндинг происходит раньше установки самого свойства и, соответственно, до вызова CoerceValueCallback! Как с этим бороться?


А>MSDN я читал, решения и даже намека на причины такой проблемы там нет. Скорее всего это не проблема DependencyProperty, а проблема биндинга. Получается, что биндинг либо происходит в обход промежуточных свойств (т.е. сразу от конечного источника к конечной цели), либо передает базовое значение. В самом источнике проверять значения очень не хотелось бы, тем более таким изощренным способом, тем более что он является оберткой над нативным кодом и наследоваться от DependencyObject возможности нет.


А что именно не получается? Что имеется ввиду под "наружу по биндингу прилетает значение не скорректированное"? Наружу — это куда? Возможно стоит привести примеры кода.
Re: Binding в WPF
От: Аноним  
Дата: 21.07.09 09:04
Оценка:
Проблема приняла более ясные очертания.
В процессе написания примера для пояснения проблемы выяснилось что при TwoWay-биндинге корректируются только значения, которые посылаются от источника к приемнику. При изменении данных в приемнике, источник получает не скорректированное значение.
Код примера будет позже. Проблема остается.
Re[3]: Binding в WPF
От: notacat  
Дата: 21.07.09 10:05
Оценка:
А>MSDN я читал, решения и даже намека на причины такой проблемы там нет. Скорее всего это не проблема DependencyProperty, а проблема биндинга. Получается, что биндинг либо происходит в обход промежуточных свойств (т.е. сразу от конечного источника к конечной цели), либо передает базовое значение. В самом источнике проверять значения очень не хотелось бы, тем более таким изощренным способом, тем более что он является оберткой над нативным кодом и наследоваться от DependencyObject возможности нет.
Подождите, совсем ничего не понятно. А кто и где у вас вызывает CoerceValueCallback, если это даже не DependencyObject?
Тогда уж показывайте код
Re: Binding в WPF
От: Аноним  
Дата: 21.07.09 14:09
Оценка:
Возможно я плохо объяснил суть проблемы.
Вот обещанный код (это просто пример):

Слайдер с корректируемым значением:

<UserControl x:Class="WpfApplication1.MySlider"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="auto" Width="auto"
    Name="mySlider">
    <Grid>
        <Slider Height="22" Width="120" Name="slider1" 
                Value="{Binding Value, ElementName=mySlider, Mode=TwoWay}" />
    </Grid>
</UserControl>


Code Behind:

    public partial class MySlider : UserControl
    {
        public MySlider()
        {
            InitializeComponent();
        }

        public decimal Value
        {
            get
            {
                return (decimal) GetValue(ValueProperty);
            }

            set
            {
                SetValue(ValueProperty, value);
            }
        }

        public static DependencyProperty ValueProperty =
            DependencyProperty.Register("Value",
                                        typeof (decimal),
                                        typeof (MySlider),
                                        new FrameworkPropertyMetadata((decimal) 0, null,CoerceValueCallback));

        private static object CoerceValueCallback(DependencyObject d, object basevalue)
        {
            return Math.Round((decimal) basevalue, 2);
        }
    }


Его использование:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my="clr-namespace:WpfApplication1"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <my:MySlider x:Name="mySlider" Height="27" VerticalAlignment="Top" Margin="28,60,119,0"
                     Value="{Binding Value, Mode=TwoWay}"/>
        <TextBox Margin="28,125,130,114" Name="textBox1" Text="{Binding Value}" />
    </Grid>
</Window>


Code Behind:


    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            DataContext = new Data {Value = 5};
        }
    }

    public class Data
    {
        public decimal Value
        {
            get;
            set;
        }
    }


В этом случае в TextBox`е мы видим значение не скорректированое (Slider выступает как цель привязки)

Если же:

        <TextBox Margin="28,125,130,114" Name="textBox1" Text="{Binding Value, ElementName=mySlider}" />


то видим то, что и ожидалось — значение округляется. В этом случае Slider — источник данных.

Что это, баг или фича? И что теперь с этим делать?
Re[2]: Binding в WPF
От: notacat  
Дата: 21.07.09 17:07
Оценка:
А>Что это, баг или фича?
На этот вопрос ответа не знаю. Скорей всего фича, если бы был баг, то его бы уже исправили.

A>И что теперь с этим делать?

Я советую в класс MySlider для ValueProperty добавить PropertyChangedCallBack и посмотреть, что там происходит. По идее, то, что я писала первый раз — в этом методе явно вызывать CoerceValue, должно сработать. Если этого почему-либо не хочется делать, то хотя бы в отладчике поизучаете, какое значение прилетает и откуда. DependencyPropertyHelper.GetValueSource(this, e.Property) вернет ValueSource, из которого это можно будет понять.


        public static DependencyProperty ValueProperty =
            DependencyProperty.Register("Value",
                                        typeof (decimal),
                                        typeof (MySlider),
                                        new FrameworkPropertyMetadata((decimal) 0, 
                                        new PropertyChangedCallback(OnValueChanged),CoerceValueCallback));

        private static object CoerceValueCallback(DependencyObject d, object basevalue)
        {
            return Math.Round((decimal) basevalue, 2);
        }
    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
            MySlider slider = d as MySlider;
            ValueSource source = DependencyPropertyHelper.GetValueSource(slider, e.Property);
            // вот тут можно поизучать source 
            if (!source.IsCoerced)
            {
                 slider.CoerceValue(e.Property);
            }
    }
Re[2]: Binding в WPF
От: MxKazan Португалия  
Дата: 21.07.09 17:07
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Возможно я плохо объяснил суть проблемы.

А>Вот обещанный код (это просто пример):
Да, не очень хорошо. Из описания казалось, что TextBox и Slider, оба, лежат в UserControl.

А>В этом случае в TextBox`е мы видим значение не скорректированое (Slider выступает как цель привязки)

А>то видим то, что и ожидалось — значение округляется. В этом случае Slider — источник данных.
А>Что это, баг или фича? И что теперь с этим делать?
Ну, это такая реализация DependencyProperty. Если поглядеть call stack при установке свойства в классе Data, то он выведет нас на DependencyObject.SetValueCommon. В этом методе мы увидим, что сначала MySlider проставляет значения через Binding:
...
bool flag2 = false;
if ((expression2 != null) && (expr == null))
{
    if (flag)
    {
        value = ((DeferredReference) value).GetValue(BaseValueSourceInternal.Local);
        flag = false;
    }
    flag2 = expression2.SetValue(this, dp, value); // здесь BindingExpression...
    entryIndex = this.CheckEntryIndex(entryIndex, dp.GlobalIndex);
}
...

и уже в самом конце SetValueCommon вызывается
...
this.UpdateEffectiveValue(entryIndex, dp, metadata, entry, ref entry2, coerceWithDeferredReference, coerceWithCurrentValue, operationType);
...

который внутри себя вызывает CoerceValueCallback

Правильно это или нет, баг или фича, я затрудняюсь ответить. Но сделано так. И в бете 4-го Framework тоже также. Поэтому, как вариант, привязаться к слайдеру. Или еще лучше, отказаться от двусторонней привязки внутри UserControl и реагировать на смену значения Slider через событие. И, кстати, для Slider не нужно указывать Mode=TwoWay, его свойство Value, аналогично TextBox.Text, в метаданных имеет флаг BindsTwoWayByDefault.
Re[3]: Binding в WPF
От: _killer  
Дата: 21.07.09 17:51
Оценка:
Здравствуйте, notacat, Вы писали:

N>Я советую в класс MySlider для ValueProperty добавить PropertyChangedCallBack и посмотреть, что там происходит. По идее, то, что я писала первый раз — в этом методе явно вызывать CoerceValue, должно сработать. Если этого почему-либо не хочется делать, то хотя бы в отладчике поизучаете, какое значение прилетает и откуда. DependencyPropertyHelper.GetValueSource(this, e.Property) вернет ValueSource, из которого это можно будет понять.


Этот подход не работает. Абсолютно ничего не меняется. Возможно после корректировки значения нужно принудительно обновлять источник, но сил и желания разбираться с этим уже нет.
Re[3]: Binding в WPF
От: _killer  
Дата: 21.07.09 17:57
Оценка:
Здравствуйте, MxKazan, Вы писали:

А>>Возможно я плохо объяснил суть проблемы.

А>>Вот обещанный код (это просто пример):
MK>Да, не очень хорошо. Из описания казалось, что TextBox и Slider, оба, лежат в UserControl.

На самом деле контрол еще сложнее, я просто выделил саму проблему

MK>Правильно это или нет, баг или фича, я затрудняюсь ответить. Но сделано так. И в бете 4-го Framework тоже также.


Это я уже и сам проверил. Подобная реализация вызывает огромное недоумение, был бы признателен, если б мне объяснили — почему так.

MK> Поэтому, как вариант, привязаться к слайдеру. Или еще лучше, отказаться от двусторонней привязки внутри UserControl и реагировать на смену MK>значения Slider через событие.


Если не найду другого способа, то придется реализовывать через события... или, скорее всего, через конвертер.
Re[4]: Binding в WPF
От: notacat  
Дата: 21.07.09 18:16
Оценка:
Я бы еще попробовала в классе MySlider убрать байндинг в xaml'e с элемента slider1, а из кода установить обратный байндинг. Т.е. не slider1.Value = 'байндинг к MySlider', а MySlider.Value = 'байндинг к slider1'
Re[5]: Binding в WPF
От: Аноним  
Дата: 22.07.09 02:42
Оценка:
Здравствуйте, notacat, Вы писали:

N>Я бы еще попробовала в классе MySlider убрать байндинг в xaml'e с элемента slider1, а из кода установить обратный байндинг. Т.е. не slider1.Value = 'байндинг к MySlider', а MySlider.Value = 'байндинг к slider1'


Я уже пытался это сделать, но получается что при установке привязки на MySlider.Value извне, привязка просто будет заменять внутреннюю, так как MySlider.Value выступает в обоих случаях как цель привязки. Сейчас попытаюсь разрулить эту ситуацию через MultiBinding и дополнительное private свойство.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.