我在一个窗口中编写一个带有TabControl的WPF桌面应用程序。我对XAML View 中的某些属性进行了数据绑定(bind),只要在.cs-file的构造函数中更改了值,该属性就可以正常工作。以后所做的更改不会显示在 View 中。
我有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 (选择要显示的屏幕并将菜单项委托(delegate)给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,但没有所有额外的绑定(bind)属性。
ViewModelBooks.cs (包含IsTile-Property):
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();
}
}
}
最佳答案
这是问题所在。在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
创建自己的viewmodel副本,该副本将替换它应该继承的DataContext:<UserControl.DataContext>
<local:ViewModelBooks />
</UserControl.DataContext>
因此,当MainWindowViewModel在其自己的ViewModelBooks副本上调用一个方法时,您可以设置一个断点并且似乎可以正常工作,因为MainWindowViewModel当然具有ViewModelBooks的完美副本-但UI中没有任何显示,因为您创建了两个副本的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();
}
最好的办法是使所有选项卡 View 模型都从同一个基类继承,该基类具有虚拟SwitchView()
方法:if (o is TabViewModelBase)
{
((TabViewModelBase)o).SwitchView();
}
这样,那种情况就可以永远处理每个子选项卡,而您不必再看那段代码。