Проблемы привязки между пользовательским элементом управления и его родителем (другим пользовательским элементом управления) MVVM

Я создаю UserControl с 2 RepeatButtons и одним TextBox. Итак, проблемы начинаются, когда я хочу привязать некоторые свойства....

К вашему сведению, я использую Caliburn.micro в качестве Framework..

  • мне нужно иметь значение свойства Interval от родителя, чтобы определить промежуток времени для обеих кнопок повтора

  • мне нужно определить MaxValue в родительском элементе и использовать его в событии MouseClick customcontrol

  • мне нужно инициализировать значение TextBox пользовательского элемента управления из родителя, использовать его в событии щелчка мыши пользовательского элемента управления, чтобы получить новое значение, обновить TextBox и использовать новое значение, измененное в родительском...

что я тестировал:

CustomRepeatButton.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;

namespace ChromiumWPF.UserControls
{
    public partial class CustomRepeatButton : UserControl
    {
        public CustomRepeatButton()
        {
            InitializeComponent();
        }

        public string TextValue
        {
            get { return (string)GetValue(TextValueProperty); }
            set { SetValue(TextValueProperty, value); }
        }

        public static DependencyProperty TextValueProperty =
           DependencyProperty.Register("TextValue", typeof(string), typeof(CustomRepeatButton));

        public int IntervalValue
        {
            get { return (int)GetValue(IntervalValueProperty); }
            set { SetValue(IntervalValueProperty, value); }
        }

        public static DependencyProperty IntervalValueProperty =
           DependencyProperty.Register("IntervalValue", 
                                       typeof(int), 
                                       typeof(CustomRepeatButton),
                                       new FrameworkPropertyMetadata(0, 
                                                                     FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
                                      );

        public int MaxValue
        {
            get { return (int)GetValue(MaxValueProperty); }
            set { SetValue(MaxValueProperty, value); }
        }

        public static DependencyProperty MaxValueProperty =
           DependencyProperty.Register("MaxValue", typeof(int), typeof(CustomRepeatButton));
    :
    :
    //same definition for ResetValue, MinValue and IncrementValue

        private void RepeatButton_Click(object sender, RoutedEventArgs e)
        {
            // click on button + or - and change the value of TextValue i trap in usercontrol Parent
            var RB = sender as RepeatButton;
        }
    }
}

CustomRepeatButton.xaml (см. Bindings TextValue и IntervalValue в стиле)

<UserControl x:Class = "ChromiumWPF.UserControls.CustomRepeatButton"
             xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local = "clr-namespace:ChromiumWPF.UserControls"
             mc:Ignorable = "d" 
             DataContext = "{Binding RelativeSource = {RelativeSource Self}}"
             d:DesignHeight = "450" d:DesignWidth = "800">
    <UserControl.Resources>
        <Style TargetType = "TextBox">
            <Style.Setters>
                <Setter Property = "FontFamily" Value = "Consolas"/>
                <Setter Property = "FontWeight" Value = "Medium"/>
                <Setter Property = "VerticalContentAlignment" Value = "Center"/>
                <Setter Property = "IsReadOnly" Value = "True"/>
                <Setter Property = "IsEnabled" Value = "True"/>
                <Setter Property = "Padding" Value = "5"/>
                <Setter Property = "Background" Value = "Aquamarine"/>
                <Setter Property = "Text" Value = "{Binding TextValue, Mode=TwoWay}"/>
            </Style.Setters>
        </Style>
        <Style TargetType = "RepeatButton">
            <Style.Setters>
                <Setter Property = "Interval" Value = "{Binding IntervalValue, Mode=TwoWay}"/>
                <Setter Property = "Background" Value = "Gray"/>
                <EventSetter Event = "Click" Handler = "RepeatButton_Click"/>
            </Style.Setters>
        </Style>
    </UserControl.Resources>
   <Grid>
        <StackPanel Orientation = "Horizontal">
            <RepeatButton Content = "-"  />
            <TextBox />
            <RepeatButton Content = "+"  />
        </StackPanel>
    </Grid>
</UserControl>

ChromeViewModel.cs

private string adress;
public string Adress
{
    get { return adress; }
    set 
    { 
        adress = value; 
        NotifyOfPropertyChange(() => Adress); 
    }
}
private int _test;
public int Test
{
    get { return _test; }
    set
    {
        _test = value;
        NotifyOfPropertyChange(() => Test);
    }
}
private int _maxV;
public int MaxV
{
    get { return _maxV; }
    set
    {
        _maxV = value;
        NotifyOfPropertyChange(() => MaxV);
    }
}

ChromeView.xaml

<UserControl x:Class = "ChromiumWPF.Views.ChromeView"
             xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d = "http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:cal = "http://www.caliburnproject.org"
             xmlns:uc = "clr-namespace:ChromiumWPF.UserControls"
             xmlns:local = "clr-namespace:ChromiumWPF.Views" 
             xmlns:vm = "clr-namespace:ChromiumWPF.ViewModels" 
             mc:Ignorable = "d" d:DataContext = "{d:DesignInstance Type=vm:ChromeViewModel}"
             d:DesignHeight = "800" d:DesignWidth = "1200">
    <Grid ShowGridLines = "True">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width = "*"/>
            <ColumnDefinition Width = "auto"/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Column = "0" Orientation = "Vertical" VerticalAlignment = "Top"
                    HorizontalAlignment = "Center">
            <ComboBox x:Name = "Months" Background = "Aqua" Height = "30" Width = "100"
                      VerticalContentAlignment = "Center"/>

            <uc:CustomRepeatButton TextValue = "{Binding Adress}"
                                   IncrementValue = "10" 
                                   IntervalValue = "{Binding Test}" 
                                   MaxValue = "{Binding MaxV}" 
                                   MinValue = "900" 
                                   ResetValue = "1080" />
        </StackPanel>
    </Grid>
</UserControl>

Итак, у меня проблема с Test, Adress и MaxV (only Bindings in relation with the customControl).

Я не понимаю, почему привязки ошибочны, определение DP и свойств кажется мне нормальным ?? это проблема DataContext? Может быть, мне нужно установить DataContext в ChromeViewModel? если да, как я мог это сделать?... Спасибо за помощь..

отображаются ошибки:

System.Windows.Data Error: 40 : BindingExpression path error: 'Adress' property not found on 'object' ''CustomRepeatButton' (Name='')'. BindingExpression:Path=Adress; DataItem='CustomRepeatButton' (Name=''); target element is 'CustomRepeatButton' (Name=''); target property is 'TextValue' (type 'String')
System.Windows.Data Error: 40 : BindingExpression path error: 'Test' property not found on 'object' ''CustomRepeatButton' (Name='')'. BindingExpression:Path=Test; DataItem='CustomRepeatButton' (Name=''); target element is 'CustomRepeatButton' (Name=''); target property is 'IntervalValue' (type 'Int32')
System.Windows.Data Error: 40 : BindingExpression path error: 'MaxV' property not found on 'object' ''CustomRepeatButton' (Name='')'. BindingExpression:Path=MaxV; DataItem='CustomRepeatButton' (Name=''); target element is 'CustomRepeatButton' (Name=''); target property is 'MaxValue' (type 'Int32')

В качестве временного теста поставьте TextBlock в ChromeView.xaml рядом с CustomRepeatButton. Попробуйте связать его свойство Text с «Адресом», как вы это делаете для CustomRepeatButton. Вы получаете ту же ошибку привязки? Я знаю, что вы сказали только привязку в отношении пользовательского элемента управления), но я просто хочу быть уверенным. Если вы получаете ту же ошибку, значит, у вас проблема с DataContext в ChromeView или есть соответствующий код, который вы не можете показать.

Joe 20.05.2023 17:26

Между прочим, это не "таможенный контроль". это UserControl. Я не пытаюсь быть здесь анальным, но разница важна.

Joe 20.05.2023 17:27

@Joe Я создал TextBox только с именем Adress, и Caliburn выполняет эту работу (связывает), и да, все в порядке

Frenchy 20.05.2023 17:34

и да, вы правы, я работаю над UserControl...

Frenchy 20.05.2023 17:42
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
54
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

В WPF система свойств зависимостей использует приоритет значений. Например, значение DataContext для FrameworkElement неявно наследуется от родителя. Если вы установите значение явно, вы переопределите унаследованное значение (родительский DataContext будет игнорироваться системой свойств).

В общем, вы хотите, чтобы пользовательский элемент управления наследовал родителей DataContext, чтобы привязки внешних данных назначались свойствам зависимостей.

Внешний Binding использует текущий DataContext в качестве источника (неявно):

<UserControl>

  <!-- Binding expects the DataContext of CustomRepeatButton 
       to be of type ChromeViewModel -->
  <uc:CustomRepeatButton TextValue = "{Binding Adress}" />
</UserControl>

Следующее локальное Binding переопределяет унаследованное значение DataContext (взятое из вашего кода):

<UserControl x:Class = "ChromiumWPF.UserControls.CustomRepeatButton"
             DataContext = "{Binding RelativeSource = {RelativeSource Self}}">

</UserControl>

Из-за этого BindingDataContext из CustomRepeatButton является самим CustomRepeatButton элементом управления. Каждый Binding, который использует синтаксис неявной привязки ({Binding path}), теперь будет связываться с CustomRepeatButton. Это объясняет сообщение об ошибке, в котором говорится, что исходный объект имеет тип CustomRepeatButton: «Ошибка пути BindingExpression: свойство «Адрес» не найдено в «объекте» «CustomRepeatButton»». Это также показывает, почему вы никогда не должны явно устанавливать DataContext пользовательского элемента управления, если только вы не хотите удивить пользователя своего элемента управления.

Чтобы исправить это, никогда не устанавливайте DataContext пользовательского элемента управления явно. Чтобы привязать внутренние элементы к свойствам элемента управления, вы должны использовать правильный источник привязки, то есть явный источник привязки.

Если внутренности назначены свойству ContentContentControl, например UserControl, вы должны использовать Binding.RelativeSource и установить свойство RelativeSource.AncestorType расширения RelativeSource:

<UserControl>
  <RepeatButton Interval = "{Binding RelativeSource = {RelativeSource AncestorType=UserControl}, Path=IntervalValue}" />
</UserControl>

Привязки одинаковы при определении в Style:

<Style TargetType = "RepeatButton">
  <Setter Property = "Interval" 
          Value = "{Binding RelativeSource = {RelativeSource AncestorType=UserControl}, Path=IntervalValue}"/>
</Style>

Если внутренние элементы определены внутри ControlTemplate, вам нужно будет использовать расширение TemplateBinding (вместо Binding) или использовать расширение Binding и установить свойство Binding.RelativeSource с помощью расширения RelativeSource и установить его RelativeSource.Mode. свойство RelativeSourceMode.TemplatedParent:


<UserControl>
  <UserControl.Template>
    <ControlTemplate TargetType = "CustomRepeatButton">
  
      <!-- Recommended using the TemplateBinding extension -->
      <RepeatButton Interval = "{TemplateBinding IntervalValue}" />

      <!-- Using the Binding together with the RelativeSource extension -->
      <RepeatButton Interval = "{Binding RelativeSource = {RelativeSource TemplatedParent}, Path=IntervalValue}" />
    </ControlTemplate>
  </UserControl.Template>
</UserControl>

Спасибо за ваш ответ, я должен читать медленно и проверять, чтобы понять...!! one, я вынужден отложить определение Datacontext из CustomRepeatButton.xaml файла, two значит мой подход с привязкой внутри Style не годится? я должен использовать ControlTemplate? так что мне нужно время, чтобы прочитать эту новую концепцию для меня...

Frenchy 20.05.2023 17:40

1) правильно 2) Нет, я пытался сказать, что привязка к DataContext неверна. Вы должны спроектировать свой элемент управления так, как будто нет DataContext. Вместо этого привязывайтесь непосредственно к свойствам пользовательского элемента управления. Я показал два сценария: а) элемент управления — это UserControl, а макет определяется как содержимое (как в вашем случае) или б) макет определяется как ControlTemplate. Поскольку Style для RepeatButton применяется в исходном контексте (местоположении в визуальном дереве), вы определяете привязки так же, как если бы вы определяли их локально. Просто используйте RelativeSource.AncestorType, как показано в моем примере.

BionicCode 20.05.2023 18:27

Я обновил ответ, чтобы показать, как выглядит привязка внутри стиля (выглядит точно так же).

BionicCode 20.05.2023 18:30

Привязка TwoWay к RadioButton.Interval избыточна. RadioButton никогда не изменит значение свойства Interval. В противном случае он был бы настроен на привязку TwoWay по умолчанию.

BionicCode 20.05.2023 18:40

Вы можете (и должны) писать <Style> <Setter ... /> </Style> вместо <Style> <Style.Setters> <Setter ... /> </Style.Setters> </Style>, как будто вы пишете не <UserControl> <UserControl.Content> <RadioButton /> </UserControl.Content> </UserControl>, а компактную форму <UserControl> <RadioButton /> </UserControl>.

BionicCode 20.05.2023 18:44

Это работает, потому что класс Style определил свойство Setters как свойство содержимого ([ContentProperty("Setters")]). Когда вы украшаете свой класс ContenPropertyAttribute, вы можете определить свойство, которому присваивается значение XAML корня. В этом случае каждый Setter, который является прямым дочерним элементом корневого тега Style, будет неявно назначен коллекции Setters. Для UserControl атрибут настроен как [ContentProperty("Content")], чтобы неявно присвоить дочерний узел корня свойству Content.

BionicCode 20.05.2023 18:48

Например, если вы настроите свой класс CustomRepeatButton для использования свойства TextValue в качестве свойства содержимого ([ContentProperty("TextValue")]), то следующий текст «example text» будет неявно назначен свойству TextValue: <CustomRepeatButton>example text</CustomRepeatButton>.

BionicCode 20.05.2023 18:53
ContentPropertyAttribute
BionicCode 20.05.2023 18:54

Спасибо за педагогический ответ. Ваш ответ очень полный и очень интересный, и он позволяет мне еще больше улучшить и понять WPF. Еще раз спасибо.

Frenchy 20.05.2023 20:16

Проблема проста. Вы пытаетесь привязать XAML элемента управления к его собственным свойствам зависимостей, но делаете это неправильно. Вам нужно установить DataContext для первого визуального элемента в элементе управления, а не для самого элемента управления.

Итак, сделайте следующее:

Сначала перейдите к файлу CustomRepeatButton.xaml. Перейдите к строке 8 (показанной ниже), где вы устанавливаете DataContext на себя. Удалить эту строку

DataContext = "{Binding RelativeSource = {RelativeSource Self}}"

Далее переходим к первому визуальному элементу. Grid. Присвойте ему значение x:Name, чтобы вы могли ссылаться на него в коде программной части. Так:

<Grid x:Name = "MainGrid" ShowGridLines = "True">

Наконец, перейдите в код элемента управления за конструктором. Вручную установите DataContext сразу после звонка на IntializeComponent. Так сделай так, чтобы это выглядело так

public CustomRepeatButton()
{
    InitializeComponent();
    MainGrid.DataContext = this;
}

Это должно дать вам то, что вы хотите.

Другие вопросы по теме