【问题标题】:Cant get a wpf textbox to bind to my player object无法让 wpf 文本框绑定到我的播放器对象
【发布时间】:2020-05-26 13:55:29
【问题描述】:

我一直在尝试将我的 Player 对象与我的 UI 绑定。 如果教程是正确的,我不明白为什么它不应该工作。 我有我的主窗口,当按下按钮时,它会在 tabPage 内创建一个 UserControl。此用户控件包含我的播放器。在主窗口中,我会将播放器传递给用户控件。 这是我的主窗口:

 public partial class MainWindow : INotifyPropertyChanged
    { 
        public MainWindow()
        {
            DataContext = this;
            InitializeComponent();
        }

        private void AddPlayerClick(Object sender, RoutedEventArgs e)
        {
            AddTabItem("New Player", new AddPlayer(new Player(1, "asd", "asd", new DateTime(1,2,3), "asd", "asd", "asd", "asd", true)));
        }
        public void AddTabItem(String name, UserControl userControl)
        {
            TabItem tab = new TabItem
            {
                Header = name
            };
            userControl.DataContext = userControl;
            tab.Content = userControl;
            TabControl.Items.Add(tab);
        }

        public event PropertyChangedEventHandler PropertyChanged;
        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] String propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

<Window x:Class="Tournament_App.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:Tournament_App"
        mc:Ignorable="d"
        Title="MainWindow" Height="1920" Width="1080">
    <StackPanel>
        <Menu>
            <MenuItem Header="Players">
                <MenuItem Header="Add Player" Click="AddPlayerClick"/>
            </MenuItem>
        </Menu>
    </StackPanel>
</Window>

单击按钮时,会创建一个新选项卡并让玩家通过。 然后在 AddPlayer 用户控件中将其设置为具有公共 get/setter 的私有属性。

    public partial class AddPlayer : UserControl, INotifyPropertyChanged
    {
        private Player _player;
        public Player Player
        {
            get { return _player; }
            set { 
                     _player = value; 
                     OnPropertyChanged();
                 }
        }

        public AddPlayer(Player player)
        {
            DataContext = Player;
            InitializeComponent();

            Player = player;
        }

        public event PropertyChangedEventHandler PropertyChanged;
        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] String propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

然后我将玩家的名字绑定到一个文本框。

<UserControl x:Class="Tournament_App.Views.AddPlayer"
             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:Tournament_App.Views"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800" >
   <StackPanel>
        <Label Content="First Name" />
        <TextBox Text="{Binding FirstName, Mode=TwoWay}"/>
   </StackPanel>

据我所知,数据上下文是正确的。而且绑定名称都是正确的,所以我不明白为什么它不起作用。任何帮助将不胜感激。我相信我更改了太多 INotifyProperty,但我无法确定我需要它们的地方。您可能会说我对 Wpf 很陌生。

【问题讨论】:

  • 您真的想在 Window 类和 UserControl 中包含所有逻辑吗?通常使用 wpf,您希望坚持使用 mvvm-pattern,其中您有一个单独的 ViewModel 类,该类用作 View 中的 DataContext(在您的情况下为 MainWindow)用于数据绑定。
  • 哦,在您的 AddPlayer 构造函数中,先尝试初始化您的 Player 属性:Player = player,然后再将其设置为 AddPlayer 的 DataContext。按照您现在的方式,您将 null 分配为 DataContext。
  • DataContext = player
  • 在 WPF 中有一条一般规则 - 如果它看起来很难,很有可能你做错了。并非总是如此,但 99% 的情况都适用,您的情况就是其中之一。首先,在 XAML 中设置数据上下文,以便编辑器可以帮助您进行类型推断(效果很好)。其次,使用 MVVM 模式,起初它看起来很臃肿,但它可以工作(尤其是在某些框架下)。根据这 2 条建议,您将正确添加选项卡项(您的版本有效,但不是您应该这样做),如果 DataContex 绑定在 XAML 中,您将不会遇到此问题。

标签: c# wpf data-binding datacontext


【解决方案1】:

首先,您将AddPlayer.DataContext 设置为AddPlayer 的构造函数中未初始化的属性Player

public AddPlayer(Player player)
{
  DataContext = Player; // Wrong! Property is not initialized.
  InitializeComponent(); // Wrong! InitializeComponent() should be the very first call

  Player = player;
}

这可能是一个错字。此外,InitializeComponent() 应该始终是第一个电话。

但是在实例化 AddPlayer 之后,你会覆盖 DataContext by setting it to theAddPlayer` 本身:

public void AddTabItem(String name, UserControl userControl)
{
  TabItem tab = new TabItem
  {
    Header = name
  };
  userControl.DataContext = userControl; // Wrong! This overrides the constructor assignment. 
  tab.Content = userControl;
  TabControl.Items.Add(tab);
}

而是将项目添加到TabControl.ItemsSource 并让控件处理DataContext。也永远不要在控件上实现INotifyPropertyChanged(或DependencyObject。始终实现DependencyProperty,因为这具有更好的性能。控件的属性也很可能是Binding.Target并绑定到数据源。Binding.Target 必须是DependencyProperty:

TabItemData.cs

public class TabItemData
{ 
  public TabItemData(string title, Player player)
  {
    this.Title = title;
    this.Player = player;
  }

  public Player Player { get; set; }
  public string Title { get; set; }
}

AddPlayer.xaml.cs

public partial class AddPlayer : UserControl, INotifyPropertyChanged
{
  public AddPlayer()
  {
    InitializeComponent();

    // Player is already the DataContext, set from XAML DataTemplate.
    // Access player like "var player = this.DataContext as Player;"
    // This instance is automatically created by a DataTemplate
  }
}

MainWindow.xaml.cs

public partial class MainWindow : Window
{ 
  public static readonly DependencyProperty PlayersProperty = DependencyProperty.Register(
  "Players",
  typeof(ObservableCollection<TabItemData>),
  typeof(MainWindow),
  new PropertyMetadata(default(ObservableCollection<TabItemData>)));

  public ObservableCollection<TabItemData> Players
  {
    get => (ObservableCollection<TabItemData>) GetValue(MainWindow.PlayersProperty);
    set => SetValue(MainWindow.PlayersProperty, value);
  }

  public MainWindow()
  {
    InitializeComponent();

    this.DataContext = this;
    this.Players = new ObservableCollection<TabItemData>();
  }

  private void AddPlayerClick(Object sender, RoutedEventArgs e)
  {
    this.Players.Add(new TabItemData("New Player", new Player(1, "asd", "asd", new DateTime(1,2,3), "asd", "asd", "asd", "asd", true)));
  }
}

MainWiindow.xaml

<Window x:Name="Window">
  <StackPanel>
    <Menu>
      <MenuItem Header="Players">
        <MenuItem Header="Add Player" Click="AddPlayerClick" />
      </MenuItem>
    </Menu>

    <TabControl ItemsSource="{Binding ElementName=Window, Path=Players>
      <TabControl.ItemTemplate>
        <DataTemplate DataType="{x:Type TabItemData}">
          <TextBlock Text="{Binding Title}" />
        </DataTemplate>
      </TabControl.ItemTemplate>
      <TabControl.ContentTemplate>
        <DataTemplate DataType="{x:Type TabItemData}">
          <AddPlayer DataContext="{Binding Player}" />
        </DataTemplate>
      </TabControl.ContentTemplate>
    </TabControl>
  </StackPanel>
</Window>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-20
    • 1970-01-01
    • 1970-01-01
    • 2015-11-04
    相关资源
    最近更新 更多