WPF数据绑定未填充选项卡

本文关键字:选项 填充 数据绑定 WPF | 更新日期: 2023-09-27 18:11:24

我在线搜索了mvvm设置,并在创建示例时注意到,在启动应用程序时,选项卡似乎不会生成。然而,我不完全清楚为什么会这样。除了没有创建标签之外,我还有一些其他问题。。。

  1. [DONE]为什么选项卡即使在设置绑定后也不会出现?(主要问题(
  2. 当用户单击"添加"时,如何将另一项添加到数据中
  3. 当只选择一个选项卡时,我如何才能使其"启用删除按钮">
  4. 当用户单击"删除"时,如果选择了选项卡,则会删除所选选项卡

我在网上找到了一些这样的例子,但很多都很复杂或不完整。我很感激你的帮助,谢谢你。

主窗口.cs

using System.Collections.ObjectModel;
using System.Windows;
namespace listBinding
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.DataContext = new ViewModel();
            InitializeComponent();
        }
        public class ViewModel
        {
            public ObservableCollection<TabItem> Tabs { get; set; }
            public ViewModel()
            {
                Tabs = new ObservableCollection<TabItem>();
                Tabs.Add(new TabItem { Header = "One", Content = "One's content" });
                Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
                Tabs.Add(new TabItem { Header = "Three", Content = "Three's content" });
            }
        }
        public class TabItem
        {
            public string Header { get; set; }
            public string Content { get; set; }
        }
        private void AddItem(object sender, RoutedEventArgs e)
        {
            // Adds new item and generates a new tab
        }
        private void DeleteItem(object sender, RoutedEventArgs e)
        {
            // Deletes the selected tab
        }
    }
}

主窗口.xaml

<Window x:Class="listBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Width="525"
        Height="350">
        <DockPanel>
        <Menu DockPanel.Dock="Top">
        <MenuItem Header="_Add" Click="AddItem"></MenuItem>
        <MenuItem Header="_Delete" Click="DeleteItem"></MenuItem>
        </Menu>

        <TabControl ItemsSource="{Binding Tabs}">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Header}" />
                </DataTemplate>
            </TabControl.ItemTemplate>
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <TextBlock
                    Text="{Binding Content}" />
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
        </DockPanel>
</Window>

WPF数据绑定未填充选项卡

好。首先,不要将DataContext放在视图的CodeBehind中。

我建议你在你的解决方案中创建一个小的文件夹层次结构,比如:

  • 转换器
  • Helper
  • 型号
  • 查看
  • ViewModel

ViewModel

在这个文件夹中有您的类,其中包含您的逻辑。视图模型对任何视图对象(xaml文件(一无所知。

在您的情况下,我会创建一个名为MainWindowViewModel的类,它看起来像:

internal class MainWindowViewModel : INotifyPropertyChanged
{
    private ICommand addCommand;
    private ObservableCollection<ContentItem> contentItems;
    private ICommand deleteCommand;
    private ContentItem selectedContentItem;
    public MainWindowViewModel()
    {
        ContentItems.Add(new ContentItem("One", "One's content"));
        ContentItems.Add(new ContentItem("Two", "Two's content"));
        ContentItems.Add(new ContentItem("Three", "Three's content"));
    }
    public ObservableCollection<ContentItem> ContentItems
    {
        get { return contentItems ?? (contentItems = new ObservableCollection<ContentItem>()); }
    }
    public ICommand AddCommand
    {
        get { return addCommand ?? (addCommand = new RelayCommand(AddContentItem)); }
    }
    public ICommand DeleteCommand
    {
        get { return deleteCommand ?? (deleteCommand = new RelayCommand(DeleteContentItem, CanDeleteContentItem)); }
    }
    public ContentItem SelectedContentItem
    {
        get { return selectedContentItem; }
        set
        {
            selectedContentItem = value;
            OnPropertyChanged();
        }
    }
    private bool CanDeleteContentItem(object parameter)
    {
        return SelectedContentItem != null;
    }
    private void DeleteContentItem(object parameter)
    {
        ContentItems.Remove(SelectedContentItem);
    }
    private void AddContentItem(object parameter)
    {
        ContentItems.Add(new ContentItem("New content item", DateTime.Now.ToLongDateString()));
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

ContentItems集合包含要在视图中显示为TabItems的所有项目。SelectedContentItem属性始终包含TabControl中当前选定的TabItem。

命令AddCommandDeleteCommand是单击"添加"或"删除"时执行的命令。在MVVM中,您通常不使用事件来进行View和ViewModel之间的通信。

助手

在这个文件夹中,我放置了一个名为RelayCommand的类,它已经在MainWindowViewModel中使用过了。类看起来是这样的:

public class RelayCommand : ICommand
{
    private readonly Predicate<object> canExecute;
    private readonly Action<object> execute;
    public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");
        this.execute = execute;
        this.canExecute = canExecute;
    }
    public bool CanExecute(object parameter)
    {
        return canExecute == null || canExecute(parameter);
    }
    public void Execute(object parameter)
    {
        execute(parameter);
    }
    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }
}

您需要这个类(或ICommand的类似实现(来在视图中要单击的对象(MenuItem、Buttons…(和相应的ViewModel之间进行交互。

型号

这是您的DataObjects。在本例中,它是具有两个属性的ContentItem。

public class ContentItem : INotifyPropertyChanged
{
    private string contentText;
    private string header;
    public ContentItem(string header, string contentText)
    {
        Header = header;
        ContentText = contentText;
    }
    public string Header
    {
        get { return header; }
        set
        {
            header = value;
            OnPropertyChanged();
        }
    }
    public string ContentText
    {
        get { return contentText; }
        set
        {
            contentText = value;
            OnPropertyChanged();
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

查看

在这个文件夹中是您的应用程序的用户可以看到的内容。在您的案例中,只有一个名为MainWindowView.xaml的文件,它看起来像:

<Window x:Class="MVVMDemo.View.MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindowView" Height="350" Width="525"
        WindowStartupLocation="CenterScreen">
    <DockPanel>
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="_Add" Command="{Binding AddCommand}"/>
            <MenuItem Header="_Delete" Command="{Binding DeleteCommand}"/>
        </Menu>
        <TabControl ItemsSource="{Binding ContentItems}" 
                    SelectedItem="{Binding SelectedContentItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Header}"/>
                </DataTemplate>
            </TabControl.ItemTemplate>
            <TabControl.ContentTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding ContentText}"/>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>
    </DockPanel>
</Window>

正如您所看到的,MenuItems绑定到ViewModel中的ICommand属性。所以你不需要在这里举办活动来进行交流。因为您的TabControl绑定到视图模型中的模型对象集合,所以您可以在TabControl中添加或删除项,只需从绑定集合中添加或移除模型对象即可。

到目前为止,视图和ViewModel之间没有任何连接。如果您现在运行代码,TabControl中将不会有任何条目。

有几种方法可以实现视图和视图模型之间的连接。

在App.xaml/App.xaml.cs:

打开App.xaml,移除零件StartupUri="MainWindow.xaml",并将其替换为Startup="App_OnStartup"。现在,您必须在App.xaml.cs中为App_OnStartup创建事件处理程序,它看起来像:

private void App_OnStartup(object sender, StartupEventArgs e)
{
    MainWindowViewModel viewModel = new MainWindowViewModel();
    MainWindowView view = new MainWindowView
    {
        DataContext = viewModel
    };
    view.ShowDialog();
}

现在你有了联系。

在主窗口视图的XAML中

将视图连接到视图模型的另一种方法是直接在xaml中设置视图的数据上下文。

要做到这一点,你必须在MainWindowViewModel中添加一个xmlns,它看起来像:xmlns:viewModel="clr-namespace:MVVMDemo.ViewModel",然后你可以在Window标签后面添加以下xaml:

<Window.DataContext>
   <viewModel:MainWindowViewModel/>
</Window.DataContext>

我希望这个样品对你有帮助。如果您对此有任何疑问,请随时询问

在了解答案之前,您需要了解MVVM:基本MVVM和ICommand使用示例

要回答您的问题:
1.a.视图的DataContext应该是ViewModel

this.DataContext = new ViewModel();
  1. b。ViewModel应该始终实现INotifyPropertyChanged。因此,当前当通过代码初始化选项卡时,它不会显示在屏幕上,因为没有通知&还有错误的数据上下文。

  2. 您需要为Add按钮使用命令绑定,该按钮应与ViewModel的AddCommand属性(ICommand类型(&然后将AddItem函数附加到命令(使用构造函数(;它将自动被反射到屏幕上,因为它是可观察的集合&实现INPC。

  3. 您可以通过两种方式完成:在Delete按钮的Visibity上使用Converter或DeleteCommand的CanExecute。

  4. 使DeleteCommand指向ViewModel中的DeleteItem((。

只需更改

this.DataContext = this;

this.DataContext = new ViewModel();

如果您观看VisualStudio输出窗口,在运行程序时,您经常会看到这些类型的错误。例如,对于您的原始代码,您会看到

"System.Windows.Data Error: 40 : BindingExpression path error: 'Tabs' property not found on 'object' ''MainWindow' (Name='')'. BindingExpression:Path=Tabs; DataItem="

您可能还发现此WPF数据绑定备忘表很有用。


对于许多WPF/mvvm应用程序,我发现mvvm Light Toolkit是一个有用的库。

您可以在上找到我关于一般模式的笔记和使用工具包的一些示例http://davisnw.github.io/mvvm-palindrome/Introduction/(示例代码可能需要进行一些更新,但基本内容应该仍然相关(。

您通过设置this.DataContext = this;MainWindow()的数据内容分配为MainWindow()本身,使其不会绑定视图模型中的内容。因此您必须将viewmodel分配为MainWindow()的dataContext。进行以下更改

更换

 public MainWindow()
        {
            this.DataContext = this;
            InitializeComponent();
        }

 public MainWindow()
        {
            this.DataContext = new ViewModel();
            InitializeComponent();
        }