英文:
Binding problems between an usercontrol and its parent (another usercontrol) MVVM
问题
这里是你的代码的翻译:
我正在创建一个UserControl,其中包含2个RepeatButtons和一个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("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 (see Bindings TextValue and IntervalValue in Style)
<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>
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: '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')
答案1
得分: 1
在WPF中,依赖属性系统使用值的优先级。例如,FrameworkElement
的DataContext
值会隐式继承自父元素。如果您明确设置该值,将会覆盖继承的值(属性系统将忽略父元素的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>
因此,由于这个Binding
,CustomRepeatButton
的DataContext
是控件本身。现在,任何使用隐式绑定语法({Binding path}
)的绑定都将绑定到CustomRepeatButton
。这解释了错误消息,说明源对象的类型是CustomRepeatButton
:"BindingExpression 路径错误:在 'CustomRepeatButton' 上未找到 'Address' 属性"。
这也解释了为什么您永远不应明确设置自定义控件的DataContext
,除非您希望让控件的用户感到意外。
为了修复这个问题,永远不要明确设置自定义控件的DataContext
。为了将内部绑定到控件的属性,您必须使用适当的绑定源,即显式绑定源。
如果内部元素分配给了ContentControl
的Content
属性,例如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):
<UserControl>
<!-- Binding expects the DataContext of CustomRepeatButton
to be of type ChromeViewModel -->
<uc:CustomRepeatButton TextValue="{Binding Adress}" />
</UserControl>
The following local Binding
overrides the inherited DataContext
value (taken from your code):
<UserControl x:Class="ChromiumWPF.UserControls.CustomRepeatButton"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
</UserControl>
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:
<UserControl>
<RepeatButton Interval="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=IntervalValue}" />
</UserControl>
Bindings are the same when defined in a Style
:
<Style TargetType="RepeatButton">
<Setter Property="Interval"
Value="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=IntervalValue}"/>
</Style>
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
:
<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>
答案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="{Binding RelativeSource={RelativeSource Self}}"
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:
<Grid x:Name="MainGrid" ShowGridLines="True">
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论