如果有人能在我發瘋之前幫助我。我有一個包含 ListBox 的用戶控制元件 我想將 SelectedItem 的屬性添加到 UserControl,以便父級可以獲取它。所以我使用了 DependencyProperty
用戶控制元件(VersionList.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>
UserControl 關聯的 ViewModel (VersionListViewModel)
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>
并在關聯的 ViewModel (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 中沒有任何反應。我想到了一個系結錯誤,但為了嘗試,我改變了這個
SelectedVersion="{Binding DataContext.SelectedVersionnnnnnn, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type Grid}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Visual Studio 告訴我 HomeViewModel 中不存在 SelectedVersionnnnn。
為什么我不能將 Selected Item 回傳到我的 HomeViewModel 的 SelectedVersion 屬性。
非常感謝你的幫助
uj5u.com熱心網友回復:
您需要解決幾個問題:
不要
DependencyObject.CoerceValue
從屬性更改的回呼中顯式呼叫。它由依賴屬性系統自動呼叫 - 在屬性更改回呼之前。您的財產不會影響布局。為了更好的性能,不要設定
FrameworkPropertyMetadataOptions.AffectsMeasure
標志,因為它會在每次屬性更改時強制執行完整的布局傳遞,這在您的情況下是不必要的。對控制元件的ListBox.SelectedItem
布局沒有影響。相反,您應該考慮通過設定FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
標志將屬性配置為默認系結兩種方式。a) 將 a 的內部結構系結
Control
到控制元件的屬性,而不是系結到DataContext
. 否則您的控制元件將變得不便處理(和撰寫)。例如,如果您更改DataContext
或重命名 回傳的物件的屬性DataContext
,您將被迫重寫內部系結以處理新的物件結構/屬性名稱。b) 這意味著您必須洗掉所有內部
DataContext
系結并為每個系結引入一個依賴屬性。例如,洗掉對屬性的ListBox.ItemsSource
系結VersionsListViewModel.Versions
并改為引入一個VersionsItemsSource
依賴屬性。例如
HomeView
:定義所有DataContext
與Binding
實際DataContext
的UserControl
(或FrameworkElement
一般情況下)相關的,而不是開始遍歷(使用)來查找與系結目標相同Binding.RelativeSource
的父級。沒有意義,只能說明你還沒有理解Binding的作業原理。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
VersionList.xaml
創作 a 時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個
HomeView.xaml
請注意,DataContext
控制元件VersionsList
的 參考了一個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
uj5u.com熱心網友回復:
在VersionList.xaml
:
<ListBox SelectedItem="{Binding SelectedVersion}" ...
這只系結ListBox.SelectedItem
到{DataContext}.SelectedVersion
. Then when a item is selected, the dependency property VersionList.SelectedVersion
isn't updated.
解決方案 1:通過視圖模型(無依賴屬性)
我認為您混淆了,因為您嘗試使用復雜的依賴屬性。一種簡單的方法是直接使用沒有依賴屬性的視圖模型。
在VersionsList.cs
中,洗掉SelectedVersionProperty
和SelectedVersion
成員。
保持VersionList.xaml
:
<UserControl x:Class="PcVueLauncher.Controls.VersionsList" />
...
<ListBox
...
ItemsSource="{Binding Versions}"
SelectedItem="{Binding SelectedVersion}">
...
</UserControl>
ListBox.SelectedItem
系結到也是如此ListBox.DataContext.SelectedVersion
。如果ListBox.DataContext
是VersionsListViewModel
,則ListBox.SelectedItem
系結到VersionsListViewModel.SelectedVersion
。
在父控制元件HomeView
中,只需要將a傳遞VersionsListViewModel
給VersionList.DataContext
:
<UserControl x:Class="PcVueLauncher.Views.HomeView"
...
<Controls:VersionsList DataContext="{Binding VersionsListViewModel}" />
...
</UserControl>
HomeView.VersionsList.ListBox.SelectedItem
系結也是如此HomeView.DataContext.VersionsListViewModel.SelectedVersion
。如果HomeView.DataContext
是HomeViewModel
,那么HomeView.VersionsList.ListBox.SelectedItem
就是系結HomeViewModel.VersionsListViewModel.SelectedVersion
。
最后,您可以洗掉該成員HomeViewModel.SelectedVersion
并使用HomeViewModel.VersionsListViewModel.SelectedVersion
.
如果你想保留成員HomeViewModel.SelectedVersion
,那么你需要重定向HomeViewModel.SelectedVersion
到HomeViewModel.VersionsListViewModel.SelectedVersion
in HomeViewModel.cs
:
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
改變了。
解決方案 2:通過依賴屬性
總而言之,您希望在選擇一個專案時將所選專案設定為VersionsList.SelectedVersion
,然后您只需要系結ListBox.SelectedItem
到VersionsList.SelectedVersion
.
SelectedVersion
首先,添加依賴屬性VersionList.cs
:
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
)
);
}
在VersionList.xaml
:
<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
(或使用下面的技巧)。
選擇什么?
使用依賴屬性,控制元件不會鏈接到視圖模型類。我將使用它來開發一個庫以在許多應用程式中重用。
使用視圖模型,控制元件需要資料背景關系中的特定成員。我將在應用程式解決方案中使用它。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/537149.html
標籤:C#wpf