从UserControl引发的WPF MVVM依赖项属性不转到用户控件容器

rxztt3cl  于 2022-11-26  发布在  其他
关注(0)|答案(2)|浏览(176)

如果有人能在我发疯之前帮助我。我有一个包含ListBox的用户控件,我想为UserControl的SelectedItem添加一个属性,这样父控件就可以获得它。所以我使用了DependencyProperty
用户控件(版本列表.xaml):

<UserControl
    x:Class="PcVueLauncher.Controls.VersionsList"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:converters="clr-namespace:PcVueLauncher.Converters"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:PcVueLauncher.Controls"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    d:Background="white"
    d:DesignHeight="450"
    d:DesignWidth="800"
    mc:Ignorable="d">
    <FrameworkElement.Resources>
        <ResourceDictionary>
            <converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
        </ResourceDictionary>
    </FrameworkElement.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <TextBlock
            Grid.Row="0"
            Padding="10"
            Text="Versions" />
        <ListBox
            Grid.Row="1"
            d:ItemsSource="{d:SampleData ItemCount=5}"
            ItemsSource="{Binding Versions}"
            SelectedItem="{Binding SelectedVersion}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="auto" />
                        </Grid.ColumnDefinitions>
                        <TextBlock
                            Grid.Column="0"
                            Margin="5,5,10,5"
                            Text="{Binding VersionName}" />
                        <Button
                            Grid.Column="1"
                            Padding="5"
                            Command="{Binding RemoveVersionCommand}"
                            Content="Remove"
                            Visibility="{Binding CanBeRemoved, Converter={StaticResource BoolToVisibilityConverter}}" />
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>

        </ListBox>
    </Grid>
</UserControl>

使用者控件相关的检视模型(版本清单检视模型)

namespace PcVueLauncher.ViewModels.Controls
{
    public class VersionsListViewModel : ViewModelBase
    {

        private List<VersionPcVue> _versions;
        public List<VersionPcVue> Versions
        {
            get
            {
                return _versions;
            }
            set
            {
                _versions = value;
                OnPropertyChanged(nameof(Versions));
            }
        }

        private VersionPcVue _selectedVersion;
        public VersionPcVue SelectedVersion
        {
            get
            {
                return _selectedVersion;
            }
            set
            {
                _selectedVersion = value;
                OnPropertyChanged(nameof(SelectedVersion));
            }
        }

        public ICommand RemoveVersionCommand { get; }

        public VersionsListViewModel()
        {
            List<VersionPcVue> versionPcVues = new()
            {
                new VersionPcVue{VersionName="V15"},
                new VersionPcVue{VersionName="V12"}
            };
            Versions = versionPcVues;
        }
    }
}

后面的使用者控件程式码(VersionList.cs):

public partial class VersionsList : UserControl
{
    public VersionsList()
    {
        InitializeComponent();
    }

    public VersionPcVue SelectedVersion
    {
        get { return (VersionPcVue)GetValue(SelectedVersionProperty); }
        set { SetValue(SelectedVersionProperty, value); }
    }

    //Using a DependencyProperty as the backing store for SelectedVersion.This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedVersionProperty =
        DependencyProperty.Register("SelectedVersion",
            typeof(VersionPcVue),
            typeof(VersionsList),
            new FrameworkPropertyMetadata(
        defaultValue: null,
        flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
        propertyChangedCallback: new PropertyChangedCallback(OnSelectionChanged)));

    private static void OnSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        d.CoerceValue(SelectedVersionProperty);
    }
}

    // Register a dependency property with the specified property name,
    // property type, owner type, property metadata, and callbacks.
    public static readonly DependencyProperty SelectedVersionProperty = DependencyProperty.Register(
    name: "SelectedVersion",
    propertyType: typeof(VersionPcVue),
    ownerType: typeof(VersionsList),
    typeMetadata: new FrameworkPropertyMetadata(
        defaultValue: null,
        flags: FrameworkPropertyMetadataOptions.AffectsMeasure,
        propertyChangedCallback: new PropertyChangedCallback(OnSelectionChanged)
    ));


    private static void OnSelectionChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        depObj.CoerceValue(SelectedVersionProperty);
    }

在包含UserControl的HomeView中,我有以下内容:

<UserControl
    x:Class="PcVueLauncher.Views.HomeView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Controls="clr-namespace:PcVueLauncher.Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:PcVueLauncher.Views"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:system="clr-namespace:System;assembly=netstandard"
    xmlns:viewmodels="clr-namespace:PcVueLauncher.ViewModels"
    d:Background="White"
    d:DataContext="{d:DesignInstance Type=viewmodels:HomeViewModel}"
    d:DesignHeight="450"
    d:DesignWidth="800"
    mc:Ignorable="d">
    <Grid>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="2*" />
        </Grid.ColumnDefinitions>

        <Controls:VersionsList
            x:Name="test"
            Grid.Column="0"
            DataContext="{Binding VersionsListViewModel}"
            SelectedVersion="{Binding DataContext.SelectedVersion, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type UserControl}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

    </Grid>
</UserControl>

并在关联的视图模型(HomeViewModel)

public class HomeViewModel : ViewModelBase
    {
        private IProjectService _projectService;

        private VersionPcVue _selectedVersion;
        public VersionPcVue SelectedVersion
        {
            get
            {
                return _selectedVersion;
            }   
            set
            {
                _selectedVersion = value;
                OnPropertyChanged(nameof(SelectedVersion));
            }
        }

        private VersionPcVue _test1;
        public VersionPcVue Test1
        {
            get
            {
                return _test1;
            }
            set
            {
                _test1 = value;
                OnPropertyChanged(nameof(Test1));
            }
        }

        private string _test;
        public string Test
        {
            get
            {
                return _test;
            }
            set
            {
                _test = value;
                OnPropertyChanged(nameof(Test));
            }
        }

        private VersionsListViewModel versionsListViewModel;
        public VersionsListViewModel VersionsListViewModel
        {
            get
            {
                return versionsListViewModel;
            }
            set
            {
                versionsListViewModel = value;
                OnPropertyChanged(nameof(VersionsListViewModel));
            }
        }

        public HomeViewModel(IProjectService projectService)
        {

            _projectService = projectService;

            VersionsListViewModel = new();
        }
    }

当我从使用者控件变更选取的项目时,HomeViewModel中没有任何React。我想到系结错误,但为了尝试,我变更了这个

SelectedVersion="{Binding DataContext.SelectedVersionnnnnnn, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type Grid}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

Visual Studio告诉我,HomeViewModel中不存在SelectedVersionnnnn。
为什么无法将选定项恢复为HomeViewModel的SelectedVersion属性。
多谢你的帮忙

0vvn1miw

0vvn1miw1#

您需要解决以下几个问题:
1.不要从属性更改回调中显式调用DependencyObject.CoerceValue。它由依赖属性系统自动调用-在属性更改回调之前。
1.您的属性不会影响配置。为了获得较佳的效能,请不要设定FrameworkPropertyMetadataOptions.AffectsMeasure旗标,因为它会在每次属性变更时胁迫执行完整的配置传递,而在您的情况下,这是不必要的。ListBox.SelectedItem不会影响控件的配置。您应该考虑设定FrameworkPropertyMetadataOptions.BindsTwoWayByDefault旗标,将属性设定为预设双向系结。

  1. a)将Control的内部绑定到控件的属性,而不是绑定到DataContext。否则,您的控件将不便于处理(和编写)。例如,如果您更改DataContext或重命名DataContext返回的对象的属性,则必须重写内部绑定以处理新的对象结构/属性名称。
    B)这意味着您必须删除所有内部DataContext绑定,并为每个绑定引入一个依赖属性。例如,删除ListBox.ItemsSourceVersionsListViewModel.Versions属性的绑定,并引入一个依赖属性,如VersionsItemsSource
    1.例如,在HomeView中:定义与UserControl的实际DataContext相关的所有DataContext相关Binding(或一般为FrameworkElement)而不是开始遍历(使用Binding.RelativeSource)来寻找与系结目的的DataContext相同的父系DataContext。这是毫无意义的,只表明你还没有理解绑定是如何工作的。

修复

1、2和3b

版本列表. xaml.cs

public partial class VersionsList : UserControl
{
    public VersionPcVue SelectedVersionItem
    {
        get => (VersionPcVue)GetValue(SelectedVersionItemProperty); 
        set => SetValue(SelectedVersionItemProperty, value); 
    }
    
    public static readonly DependencyProperty SelectedVersionItemProperty = DependencyProperty.Register(
        "SelectedVersionItem",
        typeof(VersionPcVue),
        typeof(VersionsList),
        new FrameworkPropertyMetadata(default(VersionPcVue), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedVersionChanged));

    public IList VersionsItemsSource
    {
        get => (IList)GetValue(VersionsItemsSourceProperty); 
        set => SetValue(VersionsItemsSourceProperty, value); 
    }
    
    public static readonly DependencyProperty VersionsItemsSourceProperty = DependencyProperty.Register(
        "VersionsItemsSource",
        typeof(IList),
        typeof(VersionsList),
        new PropertyMetadata(default));

    public VersionsList()
    {
        InitializeComponent();
    }

    private static void OnSelectedVersionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {        
    }
}

3a

版本列表.xaml

撰写Control时,一定要系结至控件的属性,而不是DataContext的属性:

<UserControl>
    <FrameworkElement.Resources>
        <ResourceDictionary>
            <converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
        </ResourceDictionary>
    </FrameworkElement.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <TextBlock
            Grid.Row="0"
            Padding="10"
            Text="Versions" />
        <ListBox Grid.Row="1"
                 d:ItemsSource="{d:SampleData ItemCount=5}"
                 ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=VersionsItemsSource}"
                 SelectedItem="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=SelectedVersionItem}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="auto" />
                        </Grid.ColumnDefinitions>
                        <TextBlock
                            Grid.Column="0"
                            Margin="5,5,10,5"
                            Text="{Binding VersionName}" />
                        <Button
                            Grid.Column="1"
                            Padding="5"
                            Command="{Binding RemoveVersionCommand}"
                            Content="Remove"
                            Visibility="{Binding CanBeRemoved, Converter={StaticResource BoolToVisibilityConverter}}" />
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</UserControl>

4个

主页视图.xaml

请注意,VersionsList控件的DataContext正在引用VersionsListViewModel示例。必须相应地调整所有绑定:

<UserControl>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="2*" />
        </Grid.ColumnDefinitions>

        <Controls:VersionsList x:Name="test"
                               Grid.Column="0"
                               DataContext="{Binding VersionsListViewModel}"
                               VersionsItemsSource="{Binding Versions}"
                               SelectedVersionItem="{Binding SelectedVersion}" />
    </Grid>
</UserControl>

备注

“为什么我无法将选定项恢复到HomeViewModel得SelectedVersion属性.”
根据当前的类设计和控件配置,VersionsList控件绑定到VersionsListViewModel.SelectedVersion属性。此时,您并不清楚真正想要什么。
通过让HomeViewModel侦听VersionsListViewModel.SelectedVersion属性更改来手动委托该值,或者从VersionsListViewModel删除相关属性并直接绑定到HomeViewModel.SelectedVersion。每个视图/控件的视图模型类在大多数情况下将导致错误的类设计/代码。应基于不同的考虑因素(如责任)创建单独的类。
然后,您总是希望避免重复代码(如属性和逻辑):而不是将代码复制到单独的类中。

ryevplcw

ryevplcw2#

VersionList.xaml中:

<ListBox SelectedItem="{Binding SelectedVersion}" ...

这只会将ListBox.SelectedItem绑定到{DataContext}.SelectedVersion。然后,当选择一个项时,依赖属性VersionList.SelectedVersion不会更新。
方案一:按视图模型(无依赖关系属性)
我想你搞错了,因为你试图使用一个复杂的依赖属性。一个简单的方法是直接使用视图模型,而不使用依赖属性。
VersionsList.cs中,移除SelectedVersionPropertySelectedVersion成员。
保持VersionList.xaml与:

<UserControl x:Class="PcVueLauncher.Controls.VersionsList" />
...
    <ListBox
        ...
        ItemsSource="{Binding Versions}"
        SelectedItem="{Binding SelectedVersion}">
...
</UserControl>

因此,ListBox.SelectedItem绑定到ListBox.DataContext.SelectedVersion。如果ListBox.DataContextVersionsListViewModel,则ListBox.SelectedItem绑定到VersionsListViewModel.SelectedVersion
在父控件HomeView中,它只需要传递一个VersionsListViewModelVersionList.DataContext

<UserControl x:Class="PcVueLauncher.Views.HomeView"
...
    <Controls:VersionsList DataContext="{Binding VersionsListViewModel}" />
...
</UserControl>

如果HomeView.DataContextHomeViewModel,那么HomeView.VersionsList.ListBox.SelectedItemHomeViewModel.VersionsListViewModel.SelectedVersion的绑定。
最后,您可以移除成员HomeViewModel.SelectedVersion并使用HomeViewModel.VersionsListViewModel.SelectedVersion
如果要保留成员HomeViewModel.SelectedVersion,则需要将HomeViewModel.SelectedVersion重定向到HomeViewModel.cs中的HomeViewModel.VersionsListViewModel.SelectedVersion

public class HomeViewModel : ViewModelBase
{
    private VersionsListViewModel versionsListViewModel;
    public VersionsListViewModel VersionsListViewModel
    {
        get
        {
            return versionsListViewModel;
        }
        set
        {
            if(versionsListViewModel != null)
                versionsListViewModel.PropertyChanged -= VersionsListViewModel_PropertyChanged;
            versionsListViewModel = value;
            if(versionsListViewModel != null)
                versionsListViewModel.PropertyChanged += VersionsListViewModel_PropertyChanged;
            OnPropertyChanged(nameof(VersionsListViewModel));
        }
    }

    public VersionPcVue SelectedVersion
    {
        get
        {
            return versionsListViewModel.SelectedVersion;
        }   
        set
        {
            versionsListViewModel.SelectedVersion = value;
            OnPropertyChanged(nameof(SelectedVersion));
        }
    }

    void VersionsListViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        //Propagate the property changed SelectedVersion
        if(string.IsNullOrEmpty(e.PropertyName) || e.PropertyName == nameof(VersionsListViewModel.SelectedVersion))
            OnPropertyChanged(nameof(SelectedVersion));
    }
}

诀窍是当HomeViewModel.VersionsListViewModel.SelectedVersion发生更改时,同时通知HomeViewModel.SelectedVersion也发生了更改。
方案二:依相依性属性
总而言之,当选择一个项时,您希望在VersionsList.SelectedVersion中设置所选项,那么您只需要将ListBox.SelectedItem绑定到VersionsList.SelectedVersion
首先,在VersionList.cs中添加依赖属性SelectedVersion

public partial class VersionsList : UserControl
{
    public VersionsList()
    {
        InitializeComponent();
    }

    public VersionPcVue SelectedVersion
    {
        get { return (VersionPcVue)GetValue(SelectedVersionProperty); }
        set { SetValue(SelectedVersionProperty, value); }
    }

    public static readonly DependencyProperty SelectedVersionProperty =
        DependencyProperty.Register(
            "SelectedVersion",
            typeof(VersionPcVue),
            typeof(VersionsList),
            new FrameworkPropertyMetadata(
                defaultValue: null,
                flags: FrameworkPropertyMetadataOptions.AffectsMeasure
            )
        );

    public List<VersionPcVue> Versions
    {
        get { return (List<VersionPcVue>)GetValue(VersionsProperty); }
        set { SetValue(VersionsProperty, value); }
    }
 
    public static readonly DependencyProperty VersionsProperty =
        DependencyProperty.Register(
            "Versions",
            typeof(List<VersionPcVue>),
            typeof(VersionsList),
            new FrameworkPropertyMetadata(
                defaultValue: null,
                flags: FrameworkPropertyMetadataOptions.AffectsMeasure
            )
        );
}

单位:

<UserControl x:Class="PcVueLauncher.Controls.VersionsList" />
...
    <ListBox
        ...
        ItemsSource="{Binding Versions}, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
        SelectedItem="{Binding SelectedVersion, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
...
</UserControl>

{RelativeSource TemplatedParent}指示绑定引用模板指向的元素,此处为VersionsList
若要使用控件:

<UserControl x:Class="PcVueLauncher.Views.HomeView"
...
    <Controls:VersionsList
        Versions="{Binding VersionsListViewModel.Versions}" 
        SelectedVersion="{Binding SelectedVersion}"/>
...
</UserControl>

还更改了版本以协调绑定策略。
最后,您可以删除成员VersionsListViewModel.SelectedVersion(或者使用下面的技巧)。
选择什么呢?
使用依赖属性,控件不会链接到视图模型类。我将使用这个来开发一个库,以在许多应用程序中重用。
在视图模型中,控件期望数据上下文中有特定的成员。我将在应用程序解决方案中使用这一点。

相关问题