c# TabControl 不会刷新
c# TabControl won't refresh
我正在编写一个带有 TabControl 的 WPF 桌面应用程序 window。我在 XAML 视图中对一些属性进行了数据绑定,只要在 .cs 文件的构造函数中更改了值,它就可以正常工作。稍后所做的更改不会显示在视图中。
我有 4 个文件(实际上可以做一些事情):
MainWindow.xaml(显示 TabControl + 一些按钮):
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<SystemGesture:Double x:Key="FontSize">14</SystemGesture:Double>
<SystemGesture:Double x:Key="ImageSize">26</SystemGesture:Double>
<SystemGesture:Double x:Key="MenuButtonSize">30</SystemGesture:Double>
<DataTemplate DataType="{x:Type local:ViewModelBooks}">
<local:ViewBooks/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelFiles}">
<local:ViewFiles/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelMusic}">
<local:ViewMusic />
</DataTemplate>
</Window.Resources>
<TabControl x:Name="TabControlMain" TabStripPlacement="Left"
ItemsSource="{Binding Screens}"
Background="{DynamicResource BackgroundLight}"
SelectedItem="{Binding SelectedItem}"
>
MainWindowViewModel.cs(选择要显示的屏幕并将菜单命令委托给 ViewModelBooks 对象):
namespace Bla{
public class MainWindowViewModel {
public MainWindowViewModel() {
MenuCommand = new RelayCommand(o => {
Debug.WriteLine("Menu Command " + o);
SwitchBooks(o);
});
SelectedItem = "Bla.ViewModelBooks";
}
private object _selectedItem;
public object SelectedItem {
get {
return _selectedItem;
}
set {
_selectedItem = value;
}
}
object[] _screens = new object[] { new ViewModelBooks(), new ViewModelMusic() };
public object[] Screens {
get {
return _screens;
}
}
public ICommand MenuCommand {
get; set;
}
internal void SwitchBooks(object o) {
if (o.ToString().Equals("Bla.ViewModelBooks")) {
((ViewModelBooks)_screens[0]).SwitchView();
}
}
}
public class CommandViewModel {
private MainWindowViewModel _viewmodel;
public CommandViewModel(MainWindowViewModel viewmodel) {
_viewmodel = viewmodel;
MenuCommand = new RelayCommand(o => {
_viewmodel.SwitchBooks(o);
});
}
public ICommand MenuCommand {
get; set;
}
public string Title {
get;
private set;
}
}
public class RelayCommand ...
ViewBooks.xaml(包含书籍列表。还有这个 TextBlock):
<UserControl x:Class="Bla.ViewBooks"
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:Bla"
mc:Ignorable="d"
d:DesignHeight="900" d:DesignWidth="900">
<UserControl.DataContext>
<local:ViewModelBooks />
</UserControl.DataContext>
<UserControl.Resources>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
<local:Converter x:Key="Converter" />
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView x:Name="tileView" ItemsSource="{Binding BooksToDisplay}" Visibility="{Binding IsTile, Converter={StaticResource Converter}}" Grid.Row="0">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding (FrameworkElement.ActualWidth),
RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
ItemWidth="{Binding (ListView.View).ItemWidth,
RelativeSource={RelativeSource AncestorType=ListView}}"
MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
ItemHeight="{Binding (ListView.View).ItemHeight,
RelativeSource={RelativeSource AncestorType=ListView}}" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Image Source="{Binding PicUrl}" Width="140" Height="140" Margin="10,10,10,0"/>
<TextBlock Text="{Binding Title}" Width="140" TextAlignment="Center" Margin="10,0,10,10"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Name="listView" Margin="0" ItemsSource="{Binding BooksToDisplay}" Grid.Row="0" Visibility="{Binding IsTile, Converter={StaticResource BooleanToVisibilityConverter}}">
<ListView.View>
<GridView>
<GridViewColumn Header="Titel" Width="Auto" DisplayMemberBinding="{Binding Title}" />
<GridViewColumn Header="Author" Width="Auto" DisplayMemberBinding="{Binding Author}" />
<GridViewColumn Header="Verlag" Width="Auto" DisplayMemberBinding="{Binding Publisher}" />
<GridViewColumn Header="Größe" Width="Auto" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Length}" TextAlignment="Right" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<ListView Name="blaView" Margin="0" ItemsSource="{Binding IsTileViewColl, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" >
<ListView.View>
<GridView>
<GridViewColumn Header="Titel" Width="Auto" DisplayMemberBinding="{Binding}" />
</GridView>
</ListView.View>
</ListView>
<TextBlock Text="{Binding IsTile, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" TextAlignment="Left" />
</Grid>
</UserControl>
我也尝试了没有所有额外绑定属性的 TextBlock。
ViewModelBooks.cs(包含IsTile-属性):
namespace Bla {
public class ViewModelBooks : INotifyPropertyChanged {
ObservableCollection<Book> _booksToDisplay = new ObservableCollection<Book>();
FileInfo[] _filesTxt;
private readonly string folderPath = "/folder";
public ViewModelBooks() {
Title = "Bücher";
ImgUrl = "/Resources/ic_map_white_24dp_2x.png";
_selectedView = "tiles";
DirectoryInfo di = new DirectoryInfo(folderPath);
_filesTxt = di.GetFiles("*.txt");
foreach (FileInfo file in _filesTxt) {
try {
Convert.ToInt32(file.Name.Split('_')[0]);
_booksToDisplay.Add(new Book(file));
} catch (Exception e) {
}
}
}
private string _selectedView;
public void SwitchView() {
if (_selectedView.Equals("tiles")) {
IsTile = true;
} else {
IsTile = false;
}
}
public string Title {
get; set;
}
public string ImgUrl {
get;
private set;
}
public ObservableCollection<Book> BooksToDisplay {
get => _booksToDisplay;
set => _booksToDisplay = value;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] string propertyName = null) {
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private bool _isTile;
public bool IsTile {
get {
return _isTile;
}
set {
if (_isTile == value)
return;
_isTile = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsTile"));
}
}
只要菜单命令更新 IsTile,它就可以工作。但是更新从未显示在 TextBlock
中
编辑: 现在您可以看到完整的 ViewBooks.xaml
和 ViewModelBooks.cs
。实际上,
ViewmodelBooks.cs
也有这段代码(我猜你没兴趣):
public class Book {
string _title;
string _author;
string _publisher;
int _version;
string _url;
string _thumbMD5;
string _fileMD5;
string _areaCode;
string _length;
string _picUrl;
public Book(FileInfo file) {
string oufName = file.FullName.Remove(file.FullName.Length -4, 4) + ".ouf";
FileInfo oufFile = new FileInfo(oufName);
_picUrl = file.FullName.Remove(file.FullName.Length - 4, 4) + ".png";
//_length = string.Format("{0} KB", oufFile.Length >> 10);
float lengthInM = (oufFile.Length >> 10) / 1024f;
_length = lengthInM.ToString("N2") + " MB";
try {
using (StreamReader reader = file.OpenText()) {
string line;
while ((line = reader.ReadLine()) != null) {
string[] lineSeg = line.Split(':');
switch (lineSeg[0]) {
case "Name":
_title = lineSeg[1].Trim();
break;
case "Publisher":
_publisher = lineSeg[1].Trim();
break;
case "Author":
_author = lineSeg[1].Trim();
break;
case "Book Version":
_version = Convert.ToInt32(lineSeg[1].Trim());
break;
case "URL":
_url = lineSeg[1].Trim();
break;
case "ThumbMD5":
_thumbMD5 = lineSeg[1].Trim();
break;
case "FileMD5":
_fileMD5 = lineSeg[1].Trim();
break;
case "Book Area Code":
_areaCode = lineSeg[1].Trim();
break;
}
}
}
} catch (Exception e) {
Debug.WriteLine("ALERT!!! Book-Constructor-Exception: " + e);
}
}
public string Title {
get => _title;
set => _title = value;
}
public string Author {
get => _author;
set => _author = value;
}
public string Publisher {
get => _publisher;
set => _publisher = value;
}
public int Version {
get => _version;
set => _version = value;
}
public string Url {
get => _url;
set => _url = value;
}
public string ThumbMD5 {
get => _thumbMD5;
set => _thumbMD5 = value;
}
public string FileMD5 {
get => _fileMD5;
set => _fileMD5 = value;
}
public string AreaCode {
get => _areaCode;
set => _areaCode = value;
}
public string Length {
get => _length;
set => _length = value;
}
public string PicUrl {
get => _picUrl;
set => _picUrl = value;
}
}
}
class Converter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return ((bool)value) ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}
您使用的是属性,而不是 DependencyProperty。
属性仅在构造函数中使用。普通属性不会触发事件,因此控件不知道发生了变化。
将标题 属性 替换为:
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
// Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(CommandViewModel), new PropertyMetadata(null));
如果你想根据数据绑定切换视图 属性 你可以像这样设置两个数据模板:
<DataTemplate x:Key="TilesTemplate">
<ListView ItemsSource="{Binding Items}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120"
DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="FirstName"
Width="120"
DisplayMemberBinding="{Binding FirstName}" />
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
<DataTemplate x:Key="ListTemplate">
<ListView ItemsSource="{Binding Items}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="http://server/image.png"
Width="50" />
<CheckBox Content="{Binding Name}"
IsChecked="{Binding IsChecked}"
Margin="5 5 0 0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
</DataTemplate>
并配置一个 ContentControl 的 ContentTemplate 以相应地切换 DataTemplate
<ContentControl Content="{Binding}"
Margin="5"
Grid.Row="1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate"
Value="{StaticResource TilesTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=toggle, Path=IsChecked}"
Value="True">
<Setter Property="ContentTemplate"
Value="{StaticResource ListTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Ed Plunkett 有解决方案:
Get rid of the bit in the books view XAML where you replace the DataContext. Just delete that. That's replacing the viewmodel that the main ViewModel tried to give it. Thus you have two viewmodels and the command is operating on the wrong one. This is what Rachel thought the problem might be.
非常感谢你们!我也接受 Marius 的解决方案,因为他的方式比我的方式更优雅(没有混合视图和视图模型),我正在接管 hsi 的想法。
问题来了。在 ViewBooks.xaml 中删除它,你应该没问题。
<UserControl.DataContext>
<local:ViewModelBooks />
</UserControl.DataContext>
这就是问题所在。您正在尝试使用 ViewBooks 显示 MainWindowViewModel 的 ViewModelBooks 副本,在 MainWindowViewModel 中创建:
object[] _screens = new object[] { new ViewModelBooks(), new ViewModelMusic() };
因此,您使 ViewModelBooks
实例成为选项卡的内容。您已经为 ViewModelBooks 创建了一个隐式 DataTemplate,它创建了 ViewBooks 的一个副本,并且一切正常。数据模板实例化 ,MainWindowViewModel 的 ViewModelBooks
副本作为其 DataContext。它创建了一个 ViewBooks 实例,它应该从 DataTemplate 继承它的 DataContext。
<DataTemplate DataType="{x:Type local:ViewModelBooks}">
<local:ViewBooks />
</DataTemplate>
到目前为止一切顺利。这就是应该的。
但随后 ViewBooks
创建了自己的视图模型副本,替换了它应该继承的 DataContext:
<UserControl.DataContext>
<local:ViewModelBooks />
</UserControl.DataContext>
因此,当 MainWindowViewModel 在它自己的 ViewModelBooks 副本上调用方法时,您可以设置一个断点,这似乎有效,因为 MainWindowViewModel 肯定有一个完美的 ViewModelBooks 副本——但 [=55 中没有显示任何内容=],因为您创建了两份 ViewModelBooks,而您在 UI 中看到的不是 MainWindowViewModel 拥有的那一份。
额外学分
顺便说一下,这是在 MainWindowViewModel 中创建这些东西的更好方法:
private ViewModelBooks _vmBooks = new ViewModelBooks();
private ViewModelMusic _vmMusic = new ViewModelMusic();
// Initialized in constructor
object[] _screens;
public MainWindowViewModel()
{
_screens = new object[] { _vmBooks, _vmMusic };
MenuCommand = new RelayCommand(o => {
Debug.WriteLine("Menu Command " + o);
SwitchBooks(o);
});
SelectedItem = "Bla.ViewModelBooks";
}
然后就可以使用属性了。 _screens[0]
的问题在于,有一天您可能会更改 _screens
中项目的顺序,然后您将不得不追查对 _screens
的每个引用并修复它。
if (o.ToString().Equals("Bla.ViewModelBooks"))
{
//((ViewModelBooks)_screens[0]).SwitchView();
_vmBooks.SwitchView();
}
此外,我不确定 MenuCommand 从何处获取其参数,但我怀疑您可能会这样做 - 在您进行上述建议的更改后试一试。
if (o is ViewModelBooks) {
((ViewModelBooks)o).SwitchView();
}
最好的办法是让所有选项卡视图模型都继承自同一个基 class,它有一个虚拟 SwitchView()
方法:
if (o is TabViewModelBase)
{
((TabViewModelBase)o).SwitchView();
}
然后那个案例永远处理每个子选项卡,您永远不必再看那段代码。
我正在编写一个带有 TabControl 的 WPF 桌面应用程序 window。我在 XAML 视图中对一些属性进行了数据绑定,只要在 .cs 文件的构造函数中更改了值,它就可以正常工作。稍后所做的更改不会显示在视图中。
我有 4 个文件(实际上可以做一些事情): MainWindow.xaml(显示 TabControl + 一些按钮):
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<SystemGesture:Double x:Key="FontSize">14</SystemGesture:Double>
<SystemGesture:Double x:Key="ImageSize">26</SystemGesture:Double>
<SystemGesture:Double x:Key="MenuButtonSize">30</SystemGesture:Double>
<DataTemplate DataType="{x:Type local:ViewModelBooks}">
<local:ViewBooks/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelFiles}">
<local:ViewFiles/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelMusic}">
<local:ViewMusic />
</DataTemplate>
</Window.Resources>
<TabControl x:Name="TabControlMain" TabStripPlacement="Left"
ItemsSource="{Binding Screens}"
Background="{DynamicResource BackgroundLight}"
SelectedItem="{Binding SelectedItem}"
>
MainWindowViewModel.cs(选择要显示的屏幕并将菜单命令委托给 ViewModelBooks 对象):
namespace Bla{
public class MainWindowViewModel {
public MainWindowViewModel() {
MenuCommand = new RelayCommand(o => {
Debug.WriteLine("Menu Command " + o);
SwitchBooks(o);
});
SelectedItem = "Bla.ViewModelBooks";
}
private object _selectedItem;
public object SelectedItem {
get {
return _selectedItem;
}
set {
_selectedItem = value;
}
}
object[] _screens = new object[] { new ViewModelBooks(), new ViewModelMusic() };
public object[] Screens {
get {
return _screens;
}
}
public ICommand MenuCommand {
get; set;
}
internal void SwitchBooks(object o) {
if (o.ToString().Equals("Bla.ViewModelBooks")) {
((ViewModelBooks)_screens[0]).SwitchView();
}
}
}
public class CommandViewModel {
private MainWindowViewModel _viewmodel;
public CommandViewModel(MainWindowViewModel viewmodel) {
_viewmodel = viewmodel;
MenuCommand = new RelayCommand(o => {
_viewmodel.SwitchBooks(o);
});
}
public ICommand MenuCommand {
get; set;
}
public string Title {
get;
private set;
}
}
public class RelayCommand ...
ViewBooks.xaml(包含书籍列表。还有这个 TextBlock):
<UserControl x:Class="Bla.ViewBooks"
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:Bla"
mc:Ignorable="d"
d:DesignHeight="900" d:DesignWidth="900">
<UserControl.DataContext>
<local:ViewModelBooks />
</UserControl.DataContext>
<UserControl.Resources>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
<local:Converter x:Key="Converter" />
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView x:Name="tileView" ItemsSource="{Binding BooksToDisplay}" Visibility="{Binding IsTile, Converter={StaticResource Converter}}" Grid.Row="0">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Width="{Binding (FrameworkElement.ActualWidth),
RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"
ItemWidth="{Binding (ListView.View).ItemWidth,
RelativeSource={RelativeSource AncestorType=ListView}}"
MinWidth="{Binding ItemWidth, RelativeSource={RelativeSource Self}}"
ItemHeight="{Binding (ListView.View).ItemHeight,
RelativeSource={RelativeSource AncestorType=ListView}}" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Image Source="{Binding PicUrl}" Width="140" Height="140" Margin="10,10,10,0"/>
<TextBlock Text="{Binding Title}" Width="140" TextAlignment="Center" Margin="10,0,10,10"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Name="listView" Margin="0" ItemsSource="{Binding BooksToDisplay}" Grid.Row="0" Visibility="{Binding IsTile, Converter={StaticResource BooleanToVisibilityConverter}}">
<ListView.View>
<GridView>
<GridViewColumn Header="Titel" Width="Auto" DisplayMemberBinding="{Binding Title}" />
<GridViewColumn Header="Author" Width="Auto" DisplayMemberBinding="{Binding Author}" />
<GridViewColumn Header="Verlag" Width="Auto" DisplayMemberBinding="{Binding Publisher}" />
<GridViewColumn Header="Größe" Width="Auto" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Length}" TextAlignment="Right" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
<ListView Name="blaView" Margin="0" ItemsSource="{Binding IsTileViewColl, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" >
<ListView.View>
<GridView>
<GridViewColumn Header="Titel" Width="Auto" DisplayMemberBinding="{Binding}" />
</GridView>
</ListView.View>
</ListView>
<TextBlock Text="{Binding IsTile, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" TextAlignment="Left" />
</Grid>
</UserControl>
我也尝试了没有所有额外绑定属性的 TextBlock。
ViewModelBooks.cs(包含IsTile-属性):
namespace Bla {
public class ViewModelBooks : INotifyPropertyChanged {
ObservableCollection<Book> _booksToDisplay = new ObservableCollection<Book>();
FileInfo[] _filesTxt;
private readonly string folderPath = "/folder";
public ViewModelBooks() {
Title = "Bücher";
ImgUrl = "/Resources/ic_map_white_24dp_2x.png";
_selectedView = "tiles";
DirectoryInfo di = new DirectoryInfo(folderPath);
_filesTxt = di.GetFiles("*.txt");
foreach (FileInfo file in _filesTxt) {
try {
Convert.ToInt32(file.Name.Split('_')[0]);
_booksToDisplay.Add(new Book(file));
} catch (Exception e) {
}
}
}
private string _selectedView;
public void SwitchView() {
if (_selectedView.Equals("tiles")) {
IsTile = true;
} else {
IsTile = false;
}
}
public string Title {
get; set;
}
public string ImgUrl {
get;
private set;
}
public ObservableCollection<Book> BooksToDisplay {
get => _booksToDisplay;
set => _booksToDisplay = value;
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] string propertyName = null) {
if (PropertyChanged == null)
return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private bool _isTile;
public bool IsTile {
get {
return _isTile;
}
set {
if (_isTile == value)
return;
_isTile = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsTile"));
}
}
只要菜单命令更新 IsTile,它就可以工作。但是更新从未显示在 TextBlock
中编辑: 现在您可以看到完整的 ViewBooks.xaml
和 ViewModelBooks.cs
。实际上,
ViewmodelBooks.cs
也有这段代码(我猜你没兴趣):
public class Book {
string _title;
string _author;
string _publisher;
int _version;
string _url;
string _thumbMD5;
string _fileMD5;
string _areaCode;
string _length;
string _picUrl;
public Book(FileInfo file) {
string oufName = file.FullName.Remove(file.FullName.Length -4, 4) + ".ouf";
FileInfo oufFile = new FileInfo(oufName);
_picUrl = file.FullName.Remove(file.FullName.Length - 4, 4) + ".png";
//_length = string.Format("{0} KB", oufFile.Length >> 10);
float lengthInM = (oufFile.Length >> 10) / 1024f;
_length = lengthInM.ToString("N2") + " MB";
try {
using (StreamReader reader = file.OpenText()) {
string line;
while ((line = reader.ReadLine()) != null) {
string[] lineSeg = line.Split(':');
switch (lineSeg[0]) {
case "Name":
_title = lineSeg[1].Trim();
break;
case "Publisher":
_publisher = lineSeg[1].Trim();
break;
case "Author":
_author = lineSeg[1].Trim();
break;
case "Book Version":
_version = Convert.ToInt32(lineSeg[1].Trim());
break;
case "URL":
_url = lineSeg[1].Trim();
break;
case "ThumbMD5":
_thumbMD5 = lineSeg[1].Trim();
break;
case "FileMD5":
_fileMD5 = lineSeg[1].Trim();
break;
case "Book Area Code":
_areaCode = lineSeg[1].Trim();
break;
}
}
}
} catch (Exception e) {
Debug.WriteLine("ALERT!!! Book-Constructor-Exception: " + e);
}
}
public string Title {
get => _title;
set => _title = value;
}
public string Author {
get => _author;
set => _author = value;
}
public string Publisher {
get => _publisher;
set => _publisher = value;
}
public int Version {
get => _version;
set => _version = value;
}
public string Url {
get => _url;
set => _url = value;
}
public string ThumbMD5 {
get => _thumbMD5;
set => _thumbMD5 = value;
}
public string FileMD5 {
get => _fileMD5;
set => _fileMD5 = value;
}
public string AreaCode {
get => _areaCode;
set => _areaCode = value;
}
public string Length {
get => _length;
set => _length = value;
}
public string PicUrl {
get => _picUrl;
set => _picUrl = value;
}
}
}
class Converter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return ((bool)value) ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}
您使用的是属性,而不是 DependencyProperty。 属性仅在构造函数中使用。普通属性不会触发事件,因此控件不知道发生了变化。
将标题 属性 替换为:
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
// Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(CommandViewModel), new PropertyMetadata(null));
如果你想根据数据绑定切换视图 属性 你可以像这样设置两个数据模板:
<DataTemplate x:Key="TilesTemplate">
<ListView ItemsSource="{Binding Items}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120"
DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="FirstName"
Width="120"
DisplayMemberBinding="{Binding FirstName}" />
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
<DataTemplate x:Key="ListTemplate">
<ListView ItemsSource="{Binding Items}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="http://server/image.png"
Width="50" />
<CheckBox Content="{Binding Name}"
IsChecked="{Binding IsChecked}"
Margin="5 5 0 0" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
</DataTemplate>
并配置一个 ContentControl 的 ContentTemplate 以相应地切换 DataTemplate
<ContentControl Content="{Binding}"
Margin="5"
Grid.Row="1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate"
Value="{StaticResource TilesTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=toggle, Path=IsChecked}"
Value="True">
<Setter Property="ContentTemplate"
Value="{StaticResource ListTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Ed Plunkett 有解决方案:
Get rid of the bit in the books view XAML where you replace the DataContext. Just delete that. That's replacing the viewmodel that the main ViewModel tried to give it. Thus you have two viewmodels and the command is operating on the wrong one. This is what Rachel thought the problem might be.
非常感谢你们!我也接受 Marius 的解决方案,因为他的方式比我的方式更优雅(没有混合视图和视图模型),我正在接管 hsi 的想法。
问题来了。在 ViewBooks.xaml 中删除它,你应该没问题。
<UserControl.DataContext>
<local:ViewModelBooks />
</UserControl.DataContext>
这就是问题所在。您正在尝试使用 ViewBooks 显示 MainWindowViewModel 的 ViewModelBooks 副本,在 MainWindowViewModel 中创建:
object[] _screens = new object[] { new ViewModelBooks(), new ViewModelMusic() };
因此,您使 ViewModelBooks
实例成为选项卡的内容。您已经为 ViewModelBooks 创建了一个隐式 DataTemplate,它创建了 ViewBooks 的一个副本,并且一切正常。数据模板实例化 ,MainWindowViewModel 的 ViewModelBooks
副本作为其 DataContext。它创建了一个 ViewBooks 实例,它应该从 DataTemplate 继承它的 DataContext。
<DataTemplate DataType="{x:Type local:ViewModelBooks}">
<local:ViewBooks />
</DataTemplate>
到目前为止一切顺利。这就是应该的。
但随后 ViewBooks
创建了自己的视图模型副本,替换了它应该继承的 DataContext:
<UserControl.DataContext>
<local:ViewModelBooks />
</UserControl.DataContext>
因此,当 MainWindowViewModel 在它自己的 ViewModelBooks 副本上调用方法时,您可以设置一个断点,这似乎有效,因为 MainWindowViewModel 肯定有一个完美的 ViewModelBooks 副本——但 [=55 中没有显示任何内容=],因为您创建了两份 ViewModelBooks,而您在 UI 中看到的不是 MainWindowViewModel 拥有的那一份。
额外学分
顺便说一下,这是在 MainWindowViewModel 中创建这些东西的更好方法:
private ViewModelBooks _vmBooks = new ViewModelBooks();
private ViewModelMusic _vmMusic = new ViewModelMusic();
// Initialized in constructor
object[] _screens;
public MainWindowViewModel()
{
_screens = new object[] { _vmBooks, _vmMusic };
MenuCommand = new RelayCommand(o => {
Debug.WriteLine("Menu Command " + o);
SwitchBooks(o);
});
SelectedItem = "Bla.ViewModelBooks";
}
然后就可以使用属性了。 _screens[0]
的问题在于,有一天您可能会更改 _screens
中项目的顺序,然后您将不得不追查对 _screens
的每个引用并修复它。
if (o.ToString().Equals("Bla.ViewModelBooks"))
{
//((ViewModelBooks)_screens[0]).SwitchView();
_vmBooks.SwitchView();
}
此外,我不确定 MenuCommand 从何处获取其参数,但我怀疑您可能会这样做 - 在您进行上述建议的更改后试一试。
if (o is ViewModelBooks) {
((ViewModelBooks)o).SwitchView();
}
最好的办法是让所有选项卡视图模型都继承自同一个基 class,它有一个虚拟 SwitchView()
方法:
if (o is TabViewModelBase)
{
((TabViewModelBase)o).SwitchView();
}
然后那个案例永远处理每个子选项卡,您永远不必再看那段代码。