用户控件与其父控件(另一个用户控件)之间的绑定问题 MVVM

huangapple go评论74阅读模式
英文:

Binding problems between an usercontrol and its parent (another usercontrol) MVVM

问题

这里是你的代码的翻译:

我正在创建一个UserControl,其中包含2RepeatButtons和一个TextBox。当我想要绑定一些属性时,问题就开始了...

FYI,我正在使用Caliburn.micro作为框架..

- 我需要从父级获取属性Interval的值,以定义两个重复按钮的时间间隔

- 我需要从父级获取定义的MaxValue,并在customcontrol的MouseClick事件中使用它

- 我需要初始化custom control的TextBox的值,从父级获取它,然后在custom control的MouseClick事件中使用它来获取新的值,更新TextBox并在父级中使用新的值...

我已经测试过的内容:

**CustomRepeatButton.xaml.cs**

```csharp
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));

        // 同样的定义也适用于ResetValue、MinValue和IncrementValue

        private void RepeatButton_Click(object sender, RoutedEventArgs e)
        {
            // 单击按钮+或-并更改TextValue的值,我在usercontrol的Parent中捕获它
            var RB = sender as RepeatButton;
        }
    }
}

CustomRepeatButton.xaml(查看Style中的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(与customControl相关的仅绑定)有关的问题。

我不明白为什么绑定会出错,对于我来说,DP和属性的定义似乎都没问题?这是DataContext的问题吗?也许我需要将DataContext设置为ChromeViewModel?如果是

英文:

I am creating an UserControl with 2 RepeatButtons and one TextBox. So problems begin when i want to Bind some properties....

FYI i am using Caliburn.micro as Framework..

  • i need to have the value of property Interval from the parent to define the time lapse of both repeat button

  • i need to have the MaxValue defined in the parent and use it in the event MouseClick of customcontrol

  • i need to initialize the value of TextBox of custom control from parent, use it in the event mouseclick of custom control to obtain the new value update the TextBox and use the new value changed in the parent...

what i have tested:

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(&quot;TextValue&quot;, typeof(string), typeof(CustomRepeatButton));
public int IntervalValue
{
get { return (int)GetValue(IntervalValueProperty); }
set { SetValue(IntervalValueProperty, value); }
}
public static DependencyProperty IntervalValueProperty =
DependencyProperty.Register(&quot;IntervalValue&quot;, 
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(&quot;MaxValue&quot;, 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 (see Bindings TextValue and IntervalValue in Style)

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

ChromeViewModel.cs

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

ChromeView.xaml

&lt;UserControl x:Class=&quot;ChromiumWPF.Views.ChromeView&quot;
xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot; 
xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot; 
xmlns:cal=&quot;http://www.caliburnproject.org&quot;
xmlns:uc=&quot;clr-namespace:ChromiumWPF.UserControls&quot;
xmlns:local=&quot;clr-namespace:ChromiumWPF.Views&quot; 
xmlns:vm=&quot;clr-namespace:ChromiumWPF.ViewModels&quot; 
mc:Ignorable=&quot;d&quot; d:DataContext=&quot;{d:DesignInstance Type=vm:ChromeViewModel}&quot;
d:DesignHeight=&quot;800&quot; d:DesignWidth=&quot;1200&quot;&gt;
&lt;Grid ShowGridLines=&quot;True&quot;&gt;
&lt;Grid.ColumnDefinitions&gt;
&lt;ColumnDefinition Width=&quot;*&quot;/&gt;
&lt;ColumnDefinition Width=&quot;auto&quot;/&gt;
&lt;/Grid.ColumnDefinitions&gt;
&lt;StackPanel Grid.Column=&quot;0&quot; Orientation=&quot;Vertical&quot; VerticalAlignment=&quot;Top&quot;
HorizontalAlignment=&quot;Center&quot;&gt;
&lt;ComboBox x:Name=&quot;Months&quot; Background=&quot;Aqua&quot; Height=&quot;30&quot; Width=&quot;100&quot;
VerticalContentAlignment=&quot;Center&quot;/&gt;
&lt;uc:CustomRepeatButton TextValue=&quot;{Binding Adress}&quot;
IncrementValue=&quot;10&quot; 
IntervalValue=&quot;{Binding Test}&quot; 
MaxValue=&quot;{Binding MaxV}&quot; 
MinValue=&quot;900&quot; 
ResetValue=&quot;1080&quot; /&gt;
&lt;/StackPanel&gt;
&lt;/Grid&gt;
&lt;/UserControl&gt;

So i have problem with Test, Adress and MaxV (only Bindings in relation with the customControl).

I dont see why the bindings is in error, the definition of DP and properties seems ok for me?? is it a problem of DataContext? Maybe i have to set the DataContext to ChromeViewModel? if yes how i could do that?... Thanks for help..

errors displayed:

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

答案1

得分: 1

在WPF中,依赖属性系统使用值的优先级。例如,FrameworkElementDataContext值会隐式继承自父元素。如果您明确设置该值,将会覆盖继承的值(属性系统将忽略父元素的DataContext)。

通常情况下,您希望自定义控件继承父元素的DataContext,以便外部数据绑定分配给依赖属性。

外部的Binding使用当前的DataContext作为源(隐式):

<UserControl>
<!-- Binding 期望 CustomRepeatButton 的 DataContext 为 ChromeViewModel 类型 -->
<uc:CustomRepeatButton TextValue="{Binding Address}" />
</UserControl>

以下本地的Binding会覆盖继承的DataContext值(从您的代码中取得):

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

因此,由于这个BindingCustomRepeatButtonDataContext是控件本身。现在,任何使用隐式绑定语法({Binding path})的绑定都将绑定到CustomRepeatButton。这解释了错误消息,说明源对象的类型是CustomRepeatButton:"BindingExpression 路径错误:在 'CustomRepeatButton' 上未找到 'Address' 属性"。

这也解释了为什么您永远不应明确设置自定义控件的DataContext,除非您希望让控件的用户感到意外。

为了修复这个问题,永远不要明确设置自定义控件的DataContext。为了将内部绑定到控件的属性,您必须使用适当的绑定源,即显式绑定源。

如果内部元素分配给了ContentControlContent属性,例如UserControl,您必须使用Binding.RelativeSource并设置RelativeSource扩展的RelativeSource.AncestorType属性:

<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">
<!-- 推荐使用 TemplateBinding 扩展 -->
<RepeatButton Interval="{TemplateBinding IntervalValue}" />
<!-- 使用 Binding 与 RelativeSource 扩展一起 -->
<RepeatButton Interval="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IntervalValue}" />
</ControlTemplate>
</UserControl.Template>
</UserControl>
英文:

In WPF the dependency property system uses value precedence. For example, the DataContext value of a FrameworkElement is implicitly inherited from the parent. If you set the value explicitly you will override the inherited value (the parent's DataContext will be ignored by the property system).

In general you want a custom control to inherit the parents DataContext so that external data bindings assigned to the dependency properties.

The external Binding uses the current DataContext as source (implicitly):

&lt;UserControl&gt;
&lt;!-- Binding expects the DataContext of CustomRepeatButton 
to be of type ChromeViewModel --&gt;
&lt;uc:CustomRepeatButton TextValue=&quot;{Binding Adress}&quot; /&gt;
&lt;/UserControl&gt;

The following local Binding overrides the inherited DataContext value (taken from your code):

&lt;UserControl x:Class=&quot;ChromiumWPF.UserControls.CustomRepeatButton&quot;
DataContext=&quot;{Binding RelativeSource={RelativeSource Self}}&quot;&gt;
&lt;/UserControl&gt;

Because of this Binding, the DataContext of CustomRepeatButton is the CustomRepeatButton control itself. Every Binding that uses the implicit binding syntax ({Binding path}) will now bind to the CustomRepeatButton. This explains the error message that states that the source object is of type CustomRepeatButton : "BindingExpression path error: 'Adress' property not found on 'object' ''CustomRepeatButton'".
This also exemplifies why you should never set the DataContext of a custom control explicitly - unless you want to surprise the user of your control.

To fix this don't ever set the DataContext of custom control explicitly. In order to bind the internals to the control's properties you must use the proper binding source i.e explicit binding source.

If the internals are assigned to the Content property of a ContentControl, for example of a UserControl, you must use Binding.RelativeSource and set the RelativeSource.AncestorType property of the RelativeSource extension:

&lt;UserControl&gt;
&lt;RepeatButton Interval=&quot;{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=IntervalValue}&quot; /&gt;
&lt;/UserControl&gt;

Bindings are the same when defined in a Style:

&lt;Style TargetType=&quot;RepeatButton&quot;&gt;
&lt;Setter Property=&quot;Interval&quot; 
Value=&quot;{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=IntervalValue}&quot;/&gt;
&lt;/Style&gt;

If the internals are defined inside a ControlTemplate, you would have to use the TemplateBinding extension (instead of Binding) or use the Binding extension and set the Binding.RelativeSource property using the RelativeSource extension and set its RelativeSource.Mode property to RelativeSourceMode.TemplatedParent:


&lt;UserControl&gt;
&lt;UserControl.Template&gt;
&lt;ControlTemplate TargetType=&quot;CustomRepeatButton&quot;&gt;
&lt;!-- Recommended using the TemplateBinding extension --&gt;
&lt;RepeatButton Interval=&quot;{TemplateBinding IntervalValue}&quot; /&gt;
&lt;!-- Using the Binding together with the RelativeSource extension --&gt;
&lt;RepeatButton Interval=&quot;{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IntervalValue}&quot; /&gt;
&lt;/ControlTemplate&gt;
&lt;/UserControl.Template&gt;
&lt;/UserControl&gt;

答案2

得分: 1

以下是您要翻译的部分:

问题很简单。您试图使控件的 XAML 绑定到其自己的依赖属性,但方法不对。您需要在控件内的第一个可视元素上设置 DataContext,而不是在控件本身上。

所以,请执行以下操作:

首先,进入您的 CustomRepeatButton.xaml 文件。转到第 8 行(如下所示),在那里将 DataContext 设置为自身。删除那行:

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

接下来,进入第一个可视元素,即 Grid。为其提供一个 x:Name 值,以便您可以在代码中引用它。像这样:

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

最后,进入控件的代码后台构造函数。在调用 InitializeComponent 后手动设置 DataContext。使它看起来像这样:

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

这样应该可以得到您想要的结果。

英文:

The problem is simple. You are trying to make the control's XAML bind to its own dependency properties but you are doing it the wrong way. You need to set the DataContext on the first visual element within the control, not on the control itself.

So do this:

First, go to your CustomRepeatButton.xaml file. Go to line 8 (shown below) where you set the DataContext to itself. Delete that line

DataContext=&quot;{Binding RelativeSource={RelativeSource Self}}&quot;

Next go to the first visual element. The Grid. Give it an x:Name value so you can refer to it in code-behind. Like this:

&lt;Grid x:Name=&quot;MainGrid&quot; ShowGridLines=&quot;True&quot;&gt;

Finally go into the control's code behind constructor. Manually set the DataContext right after the call to IntializeComponent. So make it look like this

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

That should get you what you want.

huangapple
  • 本文由 发表于 2023年5月20日 21:48:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76295554.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定