【问题标题】:How to Bind to Another Control Property in WPF如何在 WPF 中绑定到另一个控件属性
【发布时间】:2014-01-26 19:00:36
【问题描述】:

我正在通过命名空间使用 WPF 图表工具包:

xmlns:ChartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
xmlns:VisualizationToolkit="clr-namespace:System.Windows.Controls.DataVisualization;assembly=System.Windows.Controls.DataVisualization.Toolkit"

我有一个图表控件,每次进行Lineseries 绘图时,我都会在其中生成一个随机颜色。我删除数据点标记并使用以下样式着色

<Style x:Key="LineDataPointStyle" 
        TargetType="ChartingToolkit:LineDataPoint">
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="Width" Value="NaN"/>
    <Setter Property="Height" Value="NaN"/>
    <Setter Property="Background" 
            Value="{Binding RelativeSource={RelativeSource Self}, 
                            Converter={StaticResource ColorBrushConverter}}"/>
    <Setter Property="Template">
       <Setter.Value>
          <ControlTemplate TargetType="ChartingToolkit:LineDataPoint">
             <Grid x:Name="Root" Opacity="0"/>
          </ControlTemplate>
       </Setter.Value>
    </Setter>
</Style>

LineSeries 是通过

定义的
<ChartingToolkit:LineSeries Title="{Binding DisplayName}" 
                            AnimationSequence="FirstToLast"
                            SnapsToDevicePixels="True" 
                            DataPointStyle="{StaticResource LineDataPointStyle}"
                            ItemsSource="{Binding Data}"
                            IndependentValueBinding="{Binding X}"
                            DependentValueBinding="{Binding Y}"/>

现在,这可以正常工作,但图例标记显示的颜色与我为LineSeries 生成的随机颜色不同。我希望为 LineSeriesLineseries 本身的图例项显示相同的颜色。因此,我将图例项设置为如下

<Style x:Key="TestSuiteLegendItemStyle" 
      TargetType="{x:Type ChartingToolkit:LegendItem}">
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="Template">
       <Setter.Value>
          <ControlTemplate TargetType="{x:Type ChartingToolkit:LegendItem}">
             <Border Background="{TemplateBinding Background}" 
                     BorderBrush="{TemplateBinding BorderBrush}" 
                     BorderThickness="{TemplateBinding BorderThickness}">
                <StackPanel Orientation="Horizontal">
                   <Rectangle Width="8" 
                              Height="8" 
                              Fill="{Binding Background}" 
                              Stroke="{Binding BorderBrush}" 
                              StrokeThickness="1" Margin="0,0,3,0" />
                   <VisualizationToolkit:Title Content="{TemplateBinding Content}" />
                </StackPanel>
             </Border>
          </ControlTemplate>
       </Setter.Value>
    </Setter>
</Style>

我的问题是我怎样才能在TestSuiteLegendItemStyle 样式中“绑定”Rectangle.Fill,使其与上面定义的LineDataPointStyle 设置的LineSeries 颜色相同?*

感谢您的宝贵时间。


编辑。我已经尝试设置一个DependencyProperty 来保存我的情节的Background 颜色,如下所示通过

<Style x:Key="LineDataPointStyle" 
        TargetType="ChartingToolkit:LineDataPoint">
    ...
    <Setter Property="Background" 
            Value="{Binding RelativeSource={RelativeSource Self}, 
                            Converter={StaticResource ColorBrushConverter}}"/>
    ...
</Style>

我已经修改了转换器(如标记),以便我可以存储随机生成的背景颜色,然后在我的图例中使用它

public class ColorToBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
        object parameter, System.Globalization.CultureInfo culture)
    {
        Brush b = new SolidColorBrush(Utils.GenerateRandomColor());
        MultiChartExtensions.BackgroundBrushProperty = b; <= how to set the dependency property?
        return b;
    }

    public object ConvertBack(object value, Type targetType, 
        object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

依赖属性 (DP) 定义为

public static readonly DependencyProperty BackgroundBrushProperty;
public static void SetBackgroundBrush(DependencyObject DepObject, Brush value)
{
    DepObject.SetValue(BackgroundBrushProperty, value);
}

public static Brush GetBackgroundBrush(DependencyObject DepObject)
{
    return (Brush)DepObject.GetValue(BackgroundBrushProperty);
}

然后我希望通过这个 DP 设置图例背景

<Style x:Key="TestSuiteLegendItemStyle" 
      TargetType="{x:Type ChartingToolkit:LegendItem}">
    <Setter Property="IsTabStop" Value="False"/>
    <Setter Property="Template">
       <Setter.Value>
          <ControlTemplate TargetType="{x:Type ChartingToolkit:LegendItem}">
             <Border Background="{TemplateBinding Background}" 
                     BorderBrush="{TemplateBinding BorderBrush}" 
                     BorderThickness="{TemplateBinding BorderThickness}">
                <StackPanel Orientation="Horizontal">
                   <Rectangle Width="8" 
                              Height="8" 
                              Fill="{Binding MultiChart:MultiChartExtensions.BackgroundBrushProperty}" 
                              Stroke="{Binding BorderBrush}" 
                              StrokeThickness="1" Margin="0,0,3,0" />
                   <VisualizationToolkit:Title Content="{TemplateBinding Content}" />
                </StackPanel>
             </Border>
          </ControlTemplate>
       </Setter.Value>
    </Setter>
</Style>

对此的任何帮助将不胜感激......

【问题讨论】:

    标签: c# wpf mvvm binding charts


    【解决方案1】:

    Part I. Binding in ControlTemplate

    如果你想在 ControlTemplate 中使用 Binding,你应该使用以下构造:

    <ControlTemplate TargetType="{x:Type SomeControl}">
        <Rectangle Fill="{TemplateBinding Background}" />
    

    引用自MSDN

    TemplateBinding 是针对模板场景的 Binding 优化形式,类似于使用 {Binding RelativeSource={RelativeSource TemplatedParent}}. 构造的 Binding

    Notes about using TemplateBinding

    TemplateBinding 在模板之外或其 VisualTree 属性之外不起作用,因此您甚至不能在模板的触发器中使用 TemplateBinding。此外,TemplateBinding 在应用于 Freezable 时不起作用(主要是出于人为原因),例如 - VisualBrush。在这种情况下,可以像这样使用 Binding:

    <FreezableControl Property="{Binding RelativeSource={RelativeSource TemplatedParent},
                                         Path=Background}" />
    

    此外,您始终可以使用 TemplateBinding 的替代方法:

    <Rectangle Fill="{Binding RelativeSource={RelativeSource TemplatedParent},
                              Path=Background}" />
    

    作为另一种可能,您还可以尝试以下方法:

    <Rectangle Fill="{Binding Background, 
                              RelativeSource={RelativeSource AncestorType={x:Type SomeControl}}, 
                              Path=Background}" />
    

    Part II. Notes about your version

    在您的情况下,这可能会导致ControlTemplate 中的名称冲突,因为您已经在为边框使用绑定背景。因此,为 Border 删除此 Binding,或使用另一个属性,例如 Tagattached 依赖属性来绑定背景颜色。

    Example of using

    而不是ChartingToolkit控件,以Button控件为基础,因为这样更容易展示这种风格的思想。

    Solution 1: using Tag

    <Window.Resources>
        <Style x:Key="TestButtonStyle" TargetType="{x:Type Button}">
            <Setter Property="BorderThickness" Value="0" />
            <Setter Property="IsTabStop" Value="False" />
    
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <!-- Here we are set Tag for Border Background -->
                        <Border Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag}" 
                                BorderThickness="{TemplateBinding BorderThickness}">
    
                            <Grid>
                                <Rectangle Width="24" 
                                           Height="24" 
                                           Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}" 
                                           Stroke="{TemplateBinding BorderBrush}" />
    
                                <ContentPresenter Content="{TemplateBinding Content}" 
                                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    
    <Grid>
        <Button Name="TestButton"
                Style="{StaticResource TestButtonStyle}"
                Content="Test"
                HorizontalContentAlignment="Center"
                VerticalContentAlignment="Center"
                Tag="Green"
                Background="Aquamarine"
                Width="100"
                Height="100" />
    </Grid>
    

    Output

    这里为Rectangle,设置两种颜色:默认为Rectangle,在标签中为Border。我认为这不是一个好的解决方案,原因如下:

    • 如果一个Border和Rectangle需要设置不同的值,比如:Background、BorderThickness、BorderBrush等,一个Tag是不够的。

    • 一个名字属性必须明确它的用途,一个名字“标签”我们无话可说。

    可以得出结论,我们应该找到一个替代方案,作为替代方案,我使用具有 attached 依赖属性的扩展器类。

    扩展类ButtonExt.cs

    public static class ButtonExt
    {
        #region RectangleBackground Property
    
        public static readonly DependencyProperty RectangleBackgroundProperty;
    
        public static void SetRectangleBackground(DependencyObject DepObject, Brush value)
        {
            DepObject.SetValue(RectangleBackgroundProperty, value);
        }
    
        public static Brush GetRectangleBackground(DependencyObject DepObject)
        {
            return (Brush)DepObject.GetValue(RectangleBackgroundProperty);
        }
    
        #endregion
    
        #region RectangleBorderBrush Property
    
        public static readonly DependencyProperty RectangleBorderBrushProperty;
    
        public static void SetRectangleBorderBrush(DependencyObject DepObject, Brush value)
        {
            DepObject.SetValue(RectangleBorderBrushProperty, value);
        }
    
        public static Brush GetRectangleBorderBrush(DependencyObject DepObject)
        {
            return (Brush)DepObject.GetValue(RectangleBorderBrushProperty);
        }
    
        #endregion       
    
        #region Button Constructor
    
        static ButtonExt()
        {
            #region RectangleBackground
    
            PropertyMetadata BrushPropertyMetadata = new PropertyMetadata(Brushes.Transparent);
    
            RectangleBackgroundProperty = DependencyProperty.RegisterAttached("RectangleBackground",
                                                                typeof(Brush),
                                                                typeof(ButtonExt),
                                                                BrushPropertyMetadata);
    
            #endregion
    
            #region RectangleBorderBrush
    
            RectangleBorderBrushProperty = DependencyProperty.RegisterAttached("RectangleBorderBrush",
                                                                typeof(Brush),
                                                                typeof(ButtonExt),
                                                                BrushPropertyMetadata);
    
            #endregion
        }
    
        #endregion
    }
    

    MainWindow.xaml

    <Window.Resources>
        <Style x:Key="TestButtonExtensionStyle" TargetType="{x:Type Button}">
            <Setter Property="Width" Value="80" />
            <Setter Property="Height" Value="80" />
            <Setter Property="Background" Value="Green" />
            <Setter Property="BorderBrush" Value="Pink" />
            <Setter Property="BorderThickness" Value="4" />
    
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Border Background="{TemplateBinding Background}" 
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
    
                            <Grid>
                                <Rectangle Fill="{TemplateBinding PropertiesExtension:ButtonExt.RectangleBackground}" 
                                           Stroke="{TemplateBinding PropertiesExtension:ButtonExt.RectangleBorderBrush}"
                                           Width="30" 
                                           Height="30" />
    
                                <ContentPresenter Content="{TemplateBinding Content}" 
                                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    
    <Grid>
        <Button Style="{StaticResource TestButtonExtensionStyle}"
                PropertiesExtension:ButtonExt.RectangleBackground="Aquamarine"
                PropertiesExtension:ButtonExt.RectangleBorderBrush="Black"
                Content="Test" />
    </Grid>
    

    Output

    Part III. Setting values for dependency properties

    当你创建和注册你的 attached 依赖属性时,你必须声明 Set 和 Get 方法才能与他一起工作:

    public static void SetRectangleBackground(DependencyObject DepObject, Brush value)
    {
        DepObject.SetValue(RectangleBackgroundProperty, value);
    }
    
    public static Brush GetRectangleBackground(DependencyObject DepObject)
    {
        return (Brush)DepObject.GetValue(RectangleBackgroundProperty);
    }
    

    那么与他们的合作将如下:

    Set

    ButtonExt.SetRectangleBackground(MyButton, Brushes.Red);
    

    Get

    Brush MyBrush = ButtonExt.GetRectangleBackground(MyButton);
    

    但在我们的例子中,它并不是那么简单。当我使用附加的依赖属性时,更新值的问题都没有。但在我们的例子中,该属性在模板中,而在我的例子中,没有更新Button。我尝试在Binding和属性声明中设置Mode=TwoWayUpdateSourceTrigger=PropertyChangedGetBindingExpression().UpdateTarget(),但是没用。

    请注意,对于设置 new 值的属性,并且没有来自模板的通知,表明该属性已更新。也许我错了,你有会工作,或者它是专门制作的,例如为了避免内存泄漏。

    无论如何,最好不要直接更新依赖属性,将ModelViewModel中的属性绑定到它上面来设置值。

    例子:

    <Button Style="{StaticResource TestButtonExtensionStyle}"
            adp:ButtonExt.RectangleBackground="{Binding Path=Model.RectBackground,
                                                        Mode=TwoWay, 
                                                        UpdateSourceTrigger=PropertyChanged}"
            adp:ButtonExt.RectangleBorderBrush="{Binding Path=Model.RectBorderBrush,
                                                         Mode=TwoWay, 
                                                         UpdateSourceTrigger=PropertyChanged}" />
    

    其中RectBackgroundRectBorderBrush 实现INotifyPropertyChanged 接口。

    在这种情况下,作为替代方案,不要使用依赖属性并使用DataTemplate 作为控件。 DataTemplate 非常适合 MVVM,非常灵活和动态。

    例如,使用DataTemplate,你可以看到我的答案:

    Make (create) reusable dynamic Views

    One ViewModel for UserControl and Window or separate ViewModels

    【讨论】:

    • 再次感谢您的回复,我从您的回答中学到了很多东西。我之前看到你提到过使用Tag,但是在这里,我不清楚你打算如何将它用于合并Background?这是一个个人项目,所以我会在今晚下班后尝试实施你的建议......再次感谢。
    • 注意,我问了一个更通用的问题stackoverflow.com/questions/21293363/…,它有一个活跃的赏金。这个问题的答案将结合我在上面的新问题中所做的和你的回答......如果你的建议有效,我会很高兴稍后奖励你。
    • @Killercam:请看我的编辑。取而代之的是ChartingToolkit 控件,以Button 控件为基础,因为这种样式的想法更容易展示而且我个人没有使用ChartingToolkit。如果您的实现有问题ChartingToolkit 或有其他问题,请询问。
    • @Killercam:我想问一下,您是否对我的示例有所帮助或有什么问题?
    • 嗨,很遗憾,我还没有时间来实现它。我在上面链接的问题附带了一个赏金。如果您回答这个问题并仅链接到此问题,我将奖励您 50 个代表,因为您总是对我非常有帮助,而且您应得的!希望你一切都好,再次感谢你的帮助。一旦我实施这一点,我将接受答案...
    【解决方案2】:

    在 LineSeries 控件上设置x:Name

    <ChartingToolkit:LineSeries x:Name="lineSeries"/>
    

    然后你可以使用ElementName绑定TestSuiteLegendItemStyle:

    <Rectangle Fill="{Binding Background, ElementName=lineSeries}"/>
    

    【讨论】:

    • 感谢您的回复我应该知道这个方法!我已经尝试过了,现在将填充设置为透明。你知道为什么会这样吗?
    • 确保绑定没有中断。我怀疑这两个控件不在同一个可视树中。透明可能是默认颜色。检查输出窗口是否有任何绑定错误。
    • 这不起作用。由于某种原因,它没有通过ElementName绑定到指定的元素...
    猜你喜欢
    • 2015-09-23
    • 2012-03-24
    • 1970-01-01
    • 2011-03-03
    • 2012-12-13
    • 1970-01-01
    • 2018-01-13
    • 2012-02-08
    • 1970-01-01
    相关资源
    最近更新 更多