2015-09-07 64 views
1

我在网上搜索了mvvm安装程序,发现当我创建我的示例时,应用程序启动时看不到标签。然而,我不完全清楚为什么。除了没有创建标签,我还有一些其他问题...WPF数据绑定不填充选项卡?

  1. [完成]为什么标签不出现,即使在设置绑定后? (主要问题)
  2. 当用户点击'添加'时,它如何将其他项目添加到数据?
  3. 我该如何做到这一点,当只有选项卡被选中“删除按钮已启用”?
  4. 当用户单击“删除”时,如果选择了选项卡,则删除选定的选项卡。

我在网上发现了这些东西的一些例子,但很多都是复杂或不完整。我很感激帮助,谢谢。

的MainWindow.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 
     } 

    } 
} 

的MainWindow.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> 
+0

这对我来说看起来很混乱。给我一些时间,我建立你的样本 – Tomtom

+0

你有内部类!为什么? –

+0

@Nikita内部类只是为了举例简单,他们不必是内部类 – JokerMartini

回答

3

好的。首先,不要将DataContext放在视图的代码背后。

我建议你在你的解决方案就像创建一个小的文件夹层次结构:

  • 转换
  • 助手
  • 型号
  • 查看
  • 视图模型

ViewModel

在这个文件夹中,您的类中包含您的逻辑。视图模型对任何视图对象(xaml-file)都知之甚少。

在你的情况我会创建一个名为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-Property始终包含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)做要点击您的视图对象之间的相互作用(菜单项的,按钮,...)和对应的ViewModel。

型号

这里是你的数据对象。在这种情况下,它是具有两个属性的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绑定到视图模型的ICommand的性能。所以你不需要在这里进行交流活动。而且因为你的TabControl绑定到视图模型中的模型对象的集合,所以你可以添加或删除tabcontrol中的项目,只需从绑定集合中添加或删除模型对象即可。

现在Unti没有View和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(); 
} 

现在你所拥有的连接。

在你MainWindowView

另一种方式来连接视图到视图模型的XAML是直接在XAML设置视图的DataContext的。

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

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

我希望这个样本可以帮助您。如果您有任何疑问,请随时询问

+0

非常感谢,这是一个巨大的帮助,并且非常满意它的工作原理。你在解释一切背后的理由方面做得很好,非常感谢你,我很欣赏这一点。 – JokerMartini

+0

嘿汤姆几个问题,你有一个你自己的在线教程。似乎你会有一些很棒的教程。其次,你是否为WPF工具做过任何自由职业?如果是的话,我怎么能联系你? – JokerMartini

+0

嘿JokerMartine:我没有任何在线教程。我通过强迫自己开发MVVM模式的每一个小工具来学习我所知道的一切。我为自己设定了几个练习。只要认为自己是一个项目并使用MVVM模式进行编码即可。如果你陷入困境只是谷歌。那里总有人解决了这个问题。 – Tomtom

1

简单地改变

this.DataContext = this; 

this.DataContext = new ViewModel(); 

如果你看Visual Studio的输出窗口,你会经常很容易地看到这些类型的错误,当你运行程序。例如,你的原代码,你会看到

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

您也可以找到这个WPF DataBinding Cheatsheet很有帮助。


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

你可以在一般模式和一些使用http://davisnw.github.io/mvvm-palindrome/Introduction/工具包的示例中找到我的笔记(示例代码可能会做一些更新,但基本知识仍然适用)。

0

通过设置this.DataContext = this;,您可以将MainWindow()的Datacontext分配为MainWindow()本身,因此它不会绑定视图模型中的内容。所以你必须为MainWindow()分配viewmodel作为dataContext。作如下变更

更换

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

随着

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

你经历的答案之前,你需要得到MVVM的一个想法: Basic MVVM and ICommand Usage Example

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

this.DataContext = new ViewModel(); 
  1. 湾ViewModel应该始终执行INotifyPropertyChanged。因此,如果您正在初始化制表符,则当前通过代码时,它将不会显示在屏幕上,因为没有通知&也是错误的数据上下文。

  2. 您需要使用命令添加按钮结合,应与视图模型的AddCommand属性(ICommand的类型)被绑定&那么附加的AddItem功能的命令(使用构造函数)。新增新的TabItem可在标签列表&它会自动反映到屏幕上,因为它是可观察的集合&实现INPC。

  3. 您可以通过两种方式来实现:使用Converter on Delete按钮的Visibity或CanExecute of DeleteCommand。

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