Привязка к элементам в UserControl

#c# #wpf #binding

#c# #wpf #привязка

Вопрос:

C #, WPF.

Я пытаюсь реализовать a UserControl с элементами, привязанными к свойствам в иерархии объектов. Я использовал это в качестве ссылки.

Я создал следующий минимальный пример. Он реализует три экземпляра UserControl , причем текстовое поле в каждом случае представляет имя файла. Свойство зависимости используется для разрешения привязки. Хотя он выполняется без ошибок, текстовые поля остаются пустыми. Они должны содержать «test1», «test2» и «test3». Чего мне не хватает?

Главное окно:

 <Window x:Class="CustomControlTest.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:mycontrols="clr-namespace:MyControls"
          mc:Ignorable="d"
          Title="MainWindow" Height="100" Width="800">
      <Grid>
          <StackPanel Orientation="Vertical" DataContext="{Binding ElementName=parent}">
              <mycontrols:DataFileControl x:Name="Ctrl1" FName="{Binding Path=project.File1.Filename}"/>
              <mycontrols:DataFileControl x:Name="Ctrl2" FName="{Binding Path=project.File2.Filename}"/>
              <mycontrols:DataFileControl x:Name="Ctrl3" FName="{Binding Path=project.File3.Filename}"/>
          </StackPanel>
      </Grid>
</Window>

namespace CustomControlTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        Project project = new Project();
        public MainWindow()
        {
            InitializeComponent();
            project.File1.Filename = "test1";
            project.File2.Filename = "test2";
            project.File3.Filename = "test3";
        }
        
    }

    public class Project : INotifyPropertyChanged
    {
        public DataFile File1 { get; set; } = new DataFile();
        public DataFile File2 { get; set; } = new DataFile();
        public DataFile File3 { get; set; } = new DataFile();
    
        public event PropertyChangedEventHandler PropertyChanged;
    }

    public class DataFile : INotifyPropertyChanged
    {
        public string Filename { get; set; } = "";
    
        public event PropertyChangedEventHandler PropertyChanged;
    }  
}
  

UserControl:

 <UserControl x:Class="MyControls.DataFileControl"
             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"
             mc:Ignorable="d" 
             d:DesignHeight="40"
             d:DesignWidth="800"
             Name="TheCtrl">
    <Grid Height="20">
        <TextBox Name="filenameTextBox" Margin="5,0,5,0" Text="{Binding ElementName=TheCtrl, Path=FName, Mode=TwoWay}"/>
    </Grid>
</UserControl>

namespace MyControls
{
    public partial class DataFileControl : System.Windows.Controls.UserControl
    {

        public string FName
        {
            get { return (string)GetValue(FNameProperty); }
            set { SetValue(FNameProperty, value); }
        }

        public static readonly DependencyProperty FNameProperty =
            DependencyProperty.Register("FName", typeof(string), typeof(DataFileControl), new PropertyMetadata(null));


        public DataFileControl()
        {
            InitializeComponent();
        }

    }
}
  

Комментарии:

1. В качестве примечания, вместо того, чтобы применять имя к UserControl в его собственном XAML, вы обычно используете привязку RelativeSource, например Text="{Binding FName, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"

2. Вы также должны зарегистрировать FName свойство таким образом, чтобы оно связывало TwoWay по умолчанию. Вместо new PropertyMetadata(null) использования new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)

3. Поскольку свойство Text Text текстового поля уже связывает TwoWay по умолчанию, настройка Mode=TwoWay его привязки является избыточной, поэтому этого достаточно: Text="{Binding FName, RelativeSource={RelativeSource AncestorType=UserControl}}"

4. Спасибо 🙂 Все полезно.

Ответ №1:

Выражение

 FName="{Binding Path=project.File1.Filename}"
  

требуется общедоступное свойство с именем project в текущем DataContext, которое вы не установили. Вы должны были заметить сообщение об ошибке привязки данных в окне вывода в Visual Studio при отладке приложения.

Измените его на

 public Project project { get; } = new Project();

public MainWindow()
{
    InitializeComponent();
    project.File1.Filename = "test1";
    project.File2.Filename = "test2";
    project.File3.Filename = "test3";
    DataContext = this;
}
  

В качестве альтернативы, используйте Project экземпляр как DataContext (и, таким образом, сделайте его моделью представления)

 private readonly Project project = new Project();

public MainWindow()
{
    InitializeComponent();
    project.File1.Filename = "test1";
    project.File2.Filename = "test2";
    project.File3.Filename = "test3";
    DataContext = project;
}
  

и измените выражения привязки на

 FName="{Binding File1.Filename}"
  

Комментарии:

1. Это работает в моем минимальном примере, но на самом деле project не является свойством, и я не уверен, что смогу сделать его таковым (поскольку у него есть побочные эффекты, такие как «свойство не может быть передано как параметр ref или out»). Есть ли простое решение для этого, или это отдельный вопрос?

2. Вы видели альтернативу во второй части ответа?

3. Если где-то в вашем приложении создается новый экземпляр проекта, просто присвоите его свойству DataContext .

4. Спасибо. Пытаюсь сделать это сейчас.

5. Кажется, я обошел проблему (надеюсь). В моем реальном приложении я не смог заставить второе решение ( DataContext = project ) работать. Строка, к которой мне нужно привязаться, находится на шесть уровней ниже по иерархии объектов, и я методом проб и ошибок обнаружил, что это работает, если я увеличу количество уровней, указанных в DataContext — с, конечно, соответствующим уменьшением "{Binding ...}"