【问题标题】:How to Bind Attached Property如何绑定附加属性
【发布时间】:2016-08-02 15:13:08
【问题描述】:

我正在尝试自己学习 WPF,这有点困难。我需要知道如何通过绑定设置附加属性的值。附加属性 Grid.Row 和 Grid.RowSpan 是绑定的目标,而不是源。 StackOverflow 上也有人问过类似的问题,但这些问题是由真正了解 WPF 的人提出并回答的,或者它们涉及诸如值转换器之类的复杂问题。我还没有找到对我适用且可以理解的答案。

在这种情况下,我有一个表示一整天日程的网格,我想向其中添加事件。每个事件将从特定的网格行开始并跨越多个行,具体取决于事件的开始时间和持续时间。我的理解是你必须使用依赖属性作为绑定的来源,所以我的事件对象ActivityBlock 具有StartIncrementDurationIncrements 依赖属性来描述它在网格上的位置。

就是这样,这就是我想要做的:通过绑定在网格内创建一个 UserControl 位置。

我相信我的问题很可能出在我的 MainWindow XAML 中,在网格上设置的绑定不正确。 (请注意,我在构造函数中以编程方式创建网格行,因此不要在下面的 XAML 中查找它们。

当我运行我的应用程序时,会创建事件,但它没有显示在网格上的正确位置,就好像 Grid.Row 和 Grid.RowSpan 永远不会更新一样。绑定 Grid.Row 和 Grid.RowSpan 是不可能的吗?如果没有,我做错了什么?

这里是 MainWindow.xaml,我的问题很可能是:

<Window x:Class="DayCalendar.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:DayCalendar"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" Activated="Window_Activated"
        >
  <DockPanel LastChildFill="True">
    <Rectangle Width="50" DockPanel.Dock="Left"/>
    <Rectangle Width="50" DockPanel.Dock="Right"/>
    <Grid x:Name="TheDay" Background="#EEE">
      <ItemsControl ItemsSource="{Binding Path=Children}">
        <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
            <Grid />
          </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
          <Style TargetType="ContentPresenter">
            <Setter Property="Grid.Row" Value="{Binding Path=StartIncrement}" />
            <Setter Property="Grid.RowSpan" Value="{Binding Path=DurationIncrements}" />
          </Style>
        </ItemsControl.ItemContainerStyle>
      </ItemsControl>
    </Grid>
  </DockPanel>
</Window>

这是 MainWindow 的代码隐藏文件:

using System;
using System.Windows;
using System.Windows.Controls;
using DayCalendar.MyControls;

namespace DayCalendar {

  public partial class MainWindow : Window {

    public MainWindow() {
      InitializeComponent();
      var rowDefs = TheDay.RowDefinitions;
      rowDefs.Clear();
      for ( int ix = 0; ix < ( 60 / ActivityBlock.MINUTES_PER_INCREMENT ) * 24; ix += 1 ) {
        rowDefs.Add( new RowDefinition() );
      }
    }

    private void Window_Activated( object sender, EventArgs e ) {
      if ( m_firstActivation ) {
        m_firstActivation = false;
        var firstActivity = new ActivityBlock();
        var today = DateTime.Now.Date;
        var startTime = today.AddHours( 11.5 );
        var durationSpan = new TimeSpan( 1, 0, 0 );
        firstActivity.SetTimeSpan( startTime, durationSpan );
        TheDay.Children.Add( firstActivity );
      }
    }

    private bool m_firstActivation = true;

  }
}

这里是 ActivityBlock 代码:

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

namespace DayCalendar.MyControls {

  public partial class ActivityBlock : UserControl {

    public int StartIncrement {
      get { return ( int ) GetValue( StartIncrementProperty ); }
      set { SetValue( StartIncrementProperty, value ); }
    }

    public int DurationIncrements {
      get { return ( int ) GetValue( DurationIncrementsProperty ); }
      set { SetValue( DurationIncrementsProperty, value ); }
    }

    public ActivityBlock() {
      InitializeComponent();
    }

    public void SetTimeSpan( DateTime startTime, TimeSpan duration ) {
      int startMinute = startTime.Hour * 60 + startTime.Minute;
      int durationMinutes = ( int ) duration.TotalMinutes;
      StartIncrement = startMinute / MINUTES_PER_INCREMENT;
      DurationIncrements = Math.Max( 1, durationMinutes / MINUTES_PER_INCREMENT );
    }

    static ActivityBlock() {

      var thisType = typeof( ActivityBlock );

      var affectsArrangeAndMeasure = FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure;

      int startIncrementDefault = 0;
      StartIncrementProperty = DependencyProperty.Register(
        nameof( StartIncrement ),
        startIncrementDefault.GetType(),
        thisType,
        new FrameworkPropertyMetadata( startIncrementDefault, affectsArrangeAndMeasure )
      );

      int durationIncrementsDefault = 1;
      DurationIncrementsProperty = DependencyProperty.Register(
        nameof( DurationIncrements ),
        durationIncrementsDefault.GetType(),
        thisType,
        new FrameworkPropertyMetadata( startIncrementDefault, affectsArrangeAndMeasure )
      );

    }

    public const int MINUTES_PER_INCREMENT = 6; // 1/10th of an hour

    static public readonly DependencyProperty StartIncrementProperty;
    static public readonly DependencyProperty DurationIncrementsProperty;

  }
}

相应的 XAML 并不有趣,但我将其包括在内以备不时之需:

<UserControl x:Class="DayCalendar.MyControls.ActivityBlock"
             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:DayCalendar.MyControls"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
  <Border BorderThickness="3" BorderBrush="Black" Background="Chartreuse">
    <DockPanel LastChildFill="True">
      <TextBlock TextWrapping="WrapWithOverflow">Event Description</TextBlock>
    </DockPanel>
  </Border>
</UserControl>

【问题讨论】:

  • 我对您想要实现的目标有一个模糊的线索。但是你很难将 CodeBehind 与 xaml 混合,这是不可能实现的。问自己一件事。我是否会在 CodeBehind 中执行所有操作并能够访问我希望的每个控件,或者我应该切换到 MVVM,因为 DataBinding 仅在此处有意义。

标签: c# wpf xaml data-binding


【解决方案1】:

可以绑定Grid.Row,下面是一个简单的例子,MVVM

查看

<Window x:Class="WpfApplication3.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 <Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="20"/>
        <RowDefinition Height="20"/>
        <RowDefinition Height="20"/>
    </Grid.RowDefinitions>
    <TextBlock Text="Hello" Grid.Row="{Binding RowNumber}"></TextBlock>
 </Grid>
</Window>

视图模型

public class ViewModel
{
    public ViewModel()
    {
        RowNumber = 2;
    }

    public int RowNumber { get; set; }
}

我已将DataContext 设置为从CodeBehind 查看。如下所示,其中DataContext 链接到ViewModel 类。我们可以用不同的方式设置DataContext

xaml.cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();    
    }
}

当您启动应用程序时,它会将RowNumber 设置为2,并在第三行显示TextBlock。稍后您可以通过从 ViewModel 更新 RowNumber 来更改行

【讨论】:

  • 谢谢你,阿宾。您的示例视图非常清楚,但我确实对 ViewModel 有疑问。在您的示例中,是什么将 ViewModel 链接到 TextBlock?如果我在代码中创建第二个 TextBlock 并将其添加为子项,如何将其链接到新的 ViewModel 以便每个 TextBlock 将显示在网格上的正确空间中?
  • 我已经编辑了答案以展示如何从代码隐藏中设置 dataContext。添加更多文本块就像将新文本块添加到 TextBlocks 集合中,因此您可以添加任何容器来保存它。您在问题中使用了 ItemsControl,因此它是一个集合,可以根据绑定到它的 itemsource 的集合的数量来增长其中的控件或元素。如果需要在 ItemsControl 中设置属性,则需要使用 Styles。希望它有所帮助。谷歌是你最好的朋友。
猜你喜欢
  • 2017-07-02
  • 2011-02-07
  • 1970-01-01
  • 2021-12-01
  • 2020-12-22
  • 1970-01-01
  • 2020-02-25
  • 2011-11-01
  • 2011-02-14
相关资源
最近更新 更多