Не могу получить текстовое поле wpf для привязки к моему объекту игрока - PullRequest
0 голосов
/ 26 мая 2020

Я пытался связать свой объект Player с моим пользовательским интерфейсом. Если уроки верны, я не понимаю, почему это не должно работать. У меня есть главное окно, которое создает UserControl внутри tabPage при нажатии кнопки. Этот пользовательский элемент управления содержит мой плеер. В главном окне передам игрока в пользовательский контроль. Вот мое главное окно:

 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 он устанавливает частное свойство с помощью publi c 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.

1 Ответ

0 голосов
/ 26 мая 2020

Сначала вы устанавливаете AddPlayer.DataContext на неинициализированное свойство Player внутри конструктора AddPlayer:

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 the AddPlayer`:

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>
...