Ваша последняя попытка очень близка к работоспособному решению.Это сработало бы, если бы вы просто не сделали свойство Shared
.В самом деле, вы могли бы даже просто присвоить экземпляр RelayCommand
существующему свойству зависимости MyCommand
вместо создания нового свойства.
Тем не менее, неясно, что вы получите от такого подхода.Пользовательский элемент управления не станет универсальным, и вы могли бы реализовать этот подход с гораздо более простым в реализации обработчиком событий для события Button
элемента Click
.Итак, вот некоторые другие мысли относительно вашего вопроса и кода, содержащегося в…
Во-первых, для объекта зависимости WPF очень необычно реализовать INotifyPropertyChanged
, и еще более необычно для него сделать это дляего свойства зависимости.Если вы решите сделать это, вместо того, чтобы делать это, как здесь, путем вызова события из самого установщика свойств, вы должны вместо этого включить обратный вызов изменения свойства при регистрации свойства зависимости, например, так:
Public Shared ReadOnly CommandProperty As DependencyProperty =
DependencyProperty.Register("MyCommand", GetType(ICommand), GetType(UserControl1), New PropertyMetadata(AddressOf OnCommandPropertyChanged))
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub _RaisePropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private Shared Sub OnCommandPropertyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim userControl As UserControl1 = CType(d, UserControl1)
userControl._RaisePropertyChanged(e.Property.Name)
End Sub
Система привязки WPF обычно обновляет значение свойства зависимости напрямую, без прохождения через установщик свойства.В опубликованном вами коде это означает, что событие PropertyChanged
не будет вызываться, поскольку свойство обновляется через привязку.Вместо этого вам нужно сделать это, как указано выше, чтобы убедиться, что любое изменение свойства приведет к возникновению события PropertyChanged
.
При этом я бы не советовалреализация INotifyPropertyChanged
для объектов зависимости.Сценарии, в которых можно создать объект зависимости, как правило, являются взаимоисключающими с необходимостью реализации INotifyPropertyChanged
, поскольку объекты зависимости обычно являются целью привязки, тогда как INotifyPropertyChanged
используется для объектов, которые являются источником привязки.Единственный компонент, которому необходимо наблюдать за изменением значения свойства в целевом объекте привязки, - это система привязки WPF, и он может делать это без реализации объектом зависимости 1025 *.
Во-вторых, более идиоматично.Чтобы реализовать то, что вы намеревались сделать здесь, было бы иметь отдельный объект модели представления, в котором будут храниться фактическое значение и команду, и привязать свойства модели представления к свойствам объекта зависимости.В этом случае можно получить объект модели представления, который будет выглядеть примерно так:
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public Class UserControlViewModel
Implements INotifyPropertyChanged
Private _value As Integer
Public Property Value() As Integer
Get
Return _value
End Get
Set(value As Integer)
_UpdatePropertyField(_value, value)
End Set
End Property
Private _command As ICommand
Public Property Command() As ICommand
Get
Return _command
End Get
Set(value As ICommand)
_UpdatePropertyField(_command, value)
End Set
End Property
Public Sub New()
Command = New RelayCommand(Sub() Value += 1)
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub _UpdatePropertyField(Of T)(ByRef field As T, newValue As T, <CallerMemberName> Optional propertyName As String = Nothing)
If Not EqualityComparer(Of T).Default.Equals(field, newValue) Then
field = newValue
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End If
End Sub
End Class
(Примечание: этот класс включает метод _UpdatePropertyField()
, который обрабатывает реальный механизм изменения свойств. Как правило, фактическипоместите этот метод в базовый класс, чтобы вы могли повторно использовать эту логику в любом объекте модели представления, который можно написать.)
В приведенном выше примере модель представления устанавливает свое собственное свойство Command
в RelayCommand
объект.Если это единственный предполагаемый сценарий, который нужно поддерживать, то можно просто сделать свойство доступным только для чтения.В приведенной выше реализации также имеется возможность замены значения ICommand
по умолчанию другим выбранным объектом ICommand
(либо другой RelayCommand
, либо любой другой реализацией ICommand
).
Определив этот объект модели представления, можно затем предоставить каждому пользовательскому элементу управления свою собственную модель представления в качестве контекста данных, привязав свойства модели представления к свойствам зависимостей пользовательского элемента управления:
<Window x:Class="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:l="clr-namespace:TestSO58052597CommandProperty"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}">
<l:UserControl1.DataContext>
<l:UserControlViewModel Value="1"/>
</l:UserControl1.DataContext>
</l:UserControl1>
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}">
<l:UserControl1.DataContext>
<l:UserControlViewModel Value="1"/>
</l:UserControl1.DataContext>
</l:UserControl1>
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}">
<l:UserControl1.DataContext>
<l:UserControlViewModel Value="1"/>
</l:UserControl1.DataContext>
</l:UserControl1>
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}">
<l:UserControl1.DataContext>
<l:UserControlViewModel Value="1"/>
</l:UserControl1.DataContext>
</l:UserControl1>
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}">
<l:UserControl1.DataContext>
<l:UserControlViewModel Value="1"/>
</l:UserControl1.DataContext>
</l:UserControl1>
</StackPanel>
</Window>
Каждый объект пользовательского элемента управления получает свойсобственный объект модели представления, инициализировал XAML как значение свойства DataContext
.Затем в разметке {Binding Value}
и {Binding Command}
свойства модели представления служат источником для целевых свойств зависимостей для каждого объекта пользовательского элемента управления.
Это немного более идиоматично для WPF.Однако на самом деле все еще не совсем так, как обычно, потому что все модели представлений жестко запрограммированы для объектов управления пользователя.Когда кто-то имеет коллекцию исходных объектов и хочет представить их визуально, он обычно поддерживает разделение между данными и пользовательским интерфейсом посредством использования шаблонов и элемента ItemsControl
UI.Например:
<Window x:Class="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:l="clr-namespace:TestSO58052597CommandProperty"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<x:Array x:Key="data" Type="{x:Type l:UserControlViewModel}">
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
</x:Array>
</Window.Resources>
<ItemsControl ItemsSource="{StaticResource data}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type l:UserControlViewModel}">
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
Здесь StackPanel
, который ранее был явно установлен как элемент в окне, теперь используется в качестве шаблона для панели в элементе ItemsControl
.Сами данные теперь хранятся отдельно.В этом примере я только что использовал простой ресурс массива, но в более реалистичной программе это часто будет коллекция, на которую ссылается модель представления верхнего уровня, используемая в качестве контекста данных для окна.В любом случае, коллекция используется как значение свойства ItemsSource
в ItemsControl
.
(Примечание: для статических коллекций, как здесь, достаточно массива. Но класс ObservableCollection<T>
очень часто используется вWPF, чтобы обеспечить источник привязки для коллекций, которые могут быть изменены во время выполнения программы.)
Затем объект ItemsControl
использует шаблон данных, предоставленный для свойства ItemTemplate
, для визуального представления представления.объект модели.
В этом примере шаблон данных уникален для этого объекта ItemsControl
.Может быть желательно предоставить другой шаблон данных в другом месте, либо в другом ItemsControl
, либо при индивидуальном представлении объектов модели представления (например, через ContentControl
).Этот подход хорошо работает для таких сценариев.
Но также возможно, что можно иметь стандартную визуализацию для объекта модели представления.В этом случае можно определить используемый по умолчанию шаблон, поместив его в словарь ресурсов где-нибудь, чтобы WPF мог автоматически найти его в любом контексте, где можно использовать объект модели представления в качестве контекста данных.Тогда нет необходимости явно указывать шаблон в элементах пользовательского интерфейса, где это имеет место.
Это будет выглядеть примерно так:
<Window x:Class="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:l="clr-namespace:TestSO58052597CommandProperty"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<x:Array x:Key="data" Type="{x:Type l:UserControlViewModel}">
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
<l:UserControlViewModel Value="1"/>
</x:Array>
<DataTemplate DataType="{x:Type l:UserControlViewModel}">
<l:UserControl1 Width="40" Height="40" MyValue="{Binding Value}" MyCommand="{Binding Command}"/>
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{StaticResource data}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Window>
Это только слегка царапает поверхность по темам в WPFтакие как свойства зависимостей, привязка данных, шаблоны и т. д. На мой взгляд, следует иметь в виду следующие ключевые моменты:
- Объекты зависимостей обычно являются целью привязок
- Данные должны быть независимымивизуализации
- Не повторяйте себя.
Этот последний является критическим моментом во всем программировании, он лежит в основе ООП, и дажеболее простые сценарии, в которых вы можете создавать многократно используемые структуры данных и функции.Но в таких средах, как WPF, существует целый ряд новых аспектов, в которых есть возможность многократного использования вашего кода.Если вы обнаружите, что копируете / вставляете что-либо, имеющее отношение к вашей программе, вы, вероятно, нарушаете этот очень важный принцип.:)