我在一个窗口中编写一个带有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.xamlViewModelBooks.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创建了一个隐式Da​​taTemplate,该模板创建了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();
}
这样,那种情况就可以永远处理每个子选项卡,而您不必再看那段代码。

09-21 00:03