【问题标题】:WPF Data binding problems for Itemscontrol with Canvas as ItemsPanel使用 Canvas 作为 ItemsPanel 的 Itemscontrol 的 WPF 数据绑定问题
【发布时间】:2021-09-26 22:14:31
【问题描述】:

我遇到了绑定问题 我正在尝试将项目列表绑定到项目控件。 我确实可以正常工作,但我遇到了一些绑定错误,具体取决于 WidgetVM 的定义方式。

如果 WidgetVM 继承自 thumb(这是我想要的),它不会出现在画布上。任何其他的控制派生也是如此。现在构造函数中定义的三个项目在 Live 可视化树中的位置,但 Canvas.Left 和 Canvas .Top 属性为 NaN,我猜这就是它们没有显示的原因

还有绑定错误,说明在 MainWindow 类型上找不到 Position 属性? 我无法弄清楚为什么它在 MainWindow 而不是 WidgetVM 中查找值。当我从 Thumb 中删除继承时,这些错误就会消失。

如果我从 WidgetVM 删除继承,文本值会按预期显示在屏幕上 因此放置项目的代码有效,但当对象派生自拇指等控件时则无效。

当 WidgetVM 继承自 Thumb 因为我想使用拖动增量功能时,我如何才能让它工作。谢谢。

<ItemsControl.ItemTemplate>
    <DataTemplate>
        <TextBlock Text="{Binding Path=WidgetName}"/>
    </DataTemplate>
</ItemsControl.ItemTemplate>

<ItemsControl.ItemContainerStyle>
    <Style>
        <Setter Property="Canvas.Left" Value="{Binding Path=Position.X}"/>
        <Setter Property="Canvas.Top" Value="{Binding Path=Position.Y}"/>
    </Style>
</ItemsControl.ItemContainerStyle>

后面的代码如下

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string v)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
    }


    private ObservableCollection<Widgetvm> Widgets;
    public ObservableCollection<Widgetvm> Widgets
    {
        get { return Widgets; }
        set { Widgets = value; OnPropertyChanged("Widgets"); }
    }

public MainWindow()
{
    Widgets = new ObservableCollection<Widgetvm>()
    {
        new Widgetvm (){WidgetName="initial", Position = new Point(30,30) },
        new Widgetvm (){WidgetName="inbetween", Position = new Point(120,130) },
        new Widgetvm (){WidgetName="final", Position = new Point(330,330) },
    };
    InitializeComponent();
}
}

Widgetvm定义如下

public class Widgetvm : Thumb, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string v)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
    }

    private string WidgetName;
    public string WidgetName
    {
        get { return WidgetName; }
        set { WidgetName = value; OnPropertyChanged("WidgetName"); }
    }

    private Point position;
    public Point Position
    {
        get { return position; }
        set { position = value; OnPropertyChanged("Position"); }
    }
}

【问题讨论】:

    标签: c# wpf data-binding


    【解决方案1】:

    这不是它的工作方式。当您将项目添加到 ItemsSource 时,将呈现 DataTemplate 而不是数据项 (Widgetvm)。换句话说,在您的代码中只显示TextBlock 而没有Thumb(因为Thumb 是数据项)。
    如果要显示Thumb,则必须将其添加到DataTemplate。如果您需要为Thumb 实现额外的逻辑,请创建一个名为Widget 的新控件,该控件扩展Thumb

    • 也使用ListBox 代替ItemsControl
    • 使用nameof 而不是字符串来使用成员名称作为参数。
    • 不要在控件上实现INotifyPropertyChanged。将属性执行为DependencyProperty。这允许属性成为绑定目标或动画。它还可以提高应用程序的性能。通常,将DependecyProperty 用于扩展DependecyObject 的类,将INotifyPropertyChanged 用于所有其他类型。

    以下示例显示如何创建可拖动控件Widget,它扩展Thumb 并定位在Canvas 上:

    WidgetItem.cs
    数据模型。

    public class WidgetItem : INotifyPropertyChanged
    {
      protected virtual void OnPropertyChanged([CallerMembername] string propertyName = "")
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    
      public event PropertyChangedEventHandler PropertyChanged;
    
      private string name;
      public string Name
      {
        get => this.name; 
        set 
        { 
          this.name = value; 
          OnPropertyChanged(); 
        }
      }
    
      private Point position;
      public Point Position
      {
        get => this.position; 
        set 
        { 
          this.position = value; 
          OnPropertyChanged(); 
        }
      }
    }
    

    Widget.cs
    可拖动控件。
    创建控件库或使用现有控件库并添加以下控件。唯一重要的关键是您的项目有一个 Themes 文件夹,该文件夹必须包含 Generic.xaml 文件。

    public class Widget : Thumb
    {
      public string Name
      {
        get => (string)GetValue(NameProperty);
        set => SetValue(NameProperty, value);
      }
    
      public static readonly DependencyProperty NameProperty =  DependencyProperty.Register(
        "Name", 
        typeof(string), 
        typeof(Widget), 
        new PropertyMetadata(default));
    
      static Widget()
      {
        DefaultStyleKeyProperty.OverrideMetadata(
          typeof(Widget), 
          new FrameworkPropertyMetadata(typeof(Widget)));
      }
    
      // TODO::Implement UI logic and handle Thumb events here
    }
    

    拖拽部分没有实现,TODO:

    1. 添加依赖属性X
    2. 添加依赖属性Y
    3. 监听拖动事件并相应设置XY
    4. ListBoxDataTemplate中,将WidgetItem.Position绑定到XY

    Generic.xaml

    <Style TargetType="Widget">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="Widget">
            <Border Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
    
              <!-- Define visual appearance here. For example add a Rectangle and/or 
                   a TextBlock to display the Name  -->
    
              <TextBlock Text="{TemplateBinding Name}" />
            </Border>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
      
    

    MainWindow.xaml.cs
    定义数据源。

    public partial class MainWindow : Window
    {
      public ObservableCollection<Widgetvm> WidgetItems { get; }
    
      public MainWindow()
      {
        InitializeComponent();
    
        this.DataContext = this;
    
        this.WidgetItems = new ObservableCollection<WidgetItem>
        {
          new WidgetItem(){ Name="initial", Position = new Point(30,30) },
          new WidgetItem(){ Name="inbetween", Position = new Point(120,130) },
          new WidgetItem(){ Name="final", Position = new Point(330,330) },
        };
      }
    }
    

    MainWindow.xaml

    <Window>
      <ListBox ItemsSource="{Binding WidgetItems}">
        <ListBox.ItemPanel>
          <!-- Set to Canvas -->
        <ListBox.ItemPanel>
    
        <ListBox.ItemTemplate>
          <DataTemplate DataType="{x:Type WidgetItem}">
            <Widget Name="{Binding Name}" />
          </DataTemplate>
        </ListBox.ItemTemplate>
    
        <ListBox.ItemContainerStyle>
          <Style TargetType="ListBoxItem">
            <Setter Property="Canvas.Left" Value="{Binding Position.X}"/>
            <Setter Property="Canvas.Top" Value="{Binding Position.Y}"/>
          </Style>
        </ListBox.ItemContainerStyle>
      </ListBox>
    </Window>
    

    【讨论】:

    • 感谢您的全面回答,我将在今天晚些时候开始实施,并告诉您我的进展情况。
    猜你喜欢
    • 2011-10-23
    • 1970-01-01
    • 2011-03-10
    • 1970-01-01
    • 1970-01-01
    • 2012-09-14
    • 1970-01-01
    • 1970-01-01
    • 2018-09-30
    相关资源
    最近更新 更多