Wpf可重复使用的窗口模型/模板

时间:2013-12-18 16:12:36

标签: wpf controltemplate

我正在努力了解并学习更多关于WPF的知识。

在具有30个窗口的WPF项目中,所有窗口必须具有:

  1. 相同的工具栏(带按钮:新建,编辑,删除,查看和关闭)
  2. 相同的状态栏
  3. 每个窗口必须有一个带有2个TabItem的tabcontrol。  根据窗口,第一个TabItem的内容可以不同。第二个TabItem应始终包含根据窗口具有不同列的数据网格。
  4. “逻辑顺序”是:

    1. 打开窗口
    2. 用户在第一个TabItem中输入参数
    3. 用户点击搜索按钮
    4. 数据显示在第二个TabItem
    5. 的datagarid中
    6. 用户点击新/编辑/删除/查看
    7. 显示编辑数据的窗口
    8. 只有在选择了gridview中的行时,才能启用按钮new / edit / view和delete。 “逻辑顺序”对所有窗口都有效!

      在winform中,您可以创建一个表单,其中包含用作模板的结构,并创建30个从模型继承的窗口。 通过模板中的自定义事件,您可以了解用户是否在gridview中选择了一行,了解用户是否单击工具栏上的按钮等

      例如,当用户点击New按钮时会生成“NewItem”事件,这是一个自定义事件,然后以表格继承模型时重新启动“NewItem”事件打开输入表单

      This is the form with the first tab selected

      This is the fom with the second tab selected

      你可以在WPF中做类似的事情吗?

      您可以在WPF中创建表单模板并创建从模板继承的窗口吗?

      我希望我很清楚并抱歉我的英语不好

      谢谢

2 个答案:

答案 0 :(得分:8)

好的,我会尝试提供一个详细的答案,所以这可能会有点长,但请耐心等待。

首先,如果你正在使用WPF,那么留下你可能习惯的任何东西是非常重要的。传统的"诸如winforms之类的技术,相反,理解并拥抱The WPF Mentality

在WPF中,您不会"从基础窗口" 继承以定义应用程序的功能,因为 UI是不是应用程序。 UI只是让最终用户与应用程序交互的好方法。

相反,应用程序的交互逻辑和功能体现在一些名为ViewModels的类中,它基本上定义了UI要公开的所有内容和操作。

之后,用户界面已连接"通过DataBinding到ViewModel。

这样可以实现高水平的可重用性,可伸缩性,可维护性甚至可测试性,因为它实际上完全将UI与应用程序/业务逻辑和数据分开。

因此,这些是在WPF中执行您所描述的内容所需的基本步骤:

1 - 基本数据绑定支持

由于WPF中的双向数据绑定需要Property Change Notification,我们要做的第一件事就是创建一个基础"可绑定" class支持这个基本功能:

public class Bindable : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

2 - 父/子ViewModels

您的应用程序将要求在基类中定义父窗口功能,同时留下继承和自定义" child"因为我们将创建一个基本的通用ViewModel来定义所有常见的可重用功能,同时还定义了一个通用的" Child"属性:

public class ViewModelBase<T>: Bindable where T: Bindable
{
    private T _child;
    public T Child
    {
        get { return _child; }
        set
        {
            _child = value;
            OnPropertyChanged("Child");
        }
    }
}
  • 请注意这种结构如何适合您的要求:

enter image description here

  • 另请注意具有更改通知的属性的基本示例,您可以在属性设置器中引发OnPropertyChanged()

3 - 命令和按钮

之后,您需要为工具栏Button定义基本功能。 它们不是使用传统的Click事件处理程序方法,而是表现为Commands,它们与UI分离,其执行逻辑在ViewModel中定义,而不是代码隐藏。

为此,我们将定义一个基本的可重用DelegateCommand

//Dead-simple implementation of ICommand
//Serves as an abstraction of Actions performed by the user via interaction with the UI (for instance, Button Click)
public class Command : ICommand
{
    public Action Action { get; set; }

    public void Execute(object parameter)
    {
        if (Action != null)
            Action();
    }

    public bool CanExecute(object parameter)
    {
        return IsEnabled;
    }

    private bool _isEnabled = true;
    public bool IsEnabled
    {
        get { return _isEnabled; }
        set
        {
            _isEnabled = value;
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, EventArgs.Empty);
        }
    }

    public event EventHandler CanExecuteChanged;

    public Command(Action action)
    {
        Action = action;
    }
}

使用此命令类,我们现在可以在主ViewModel中定义所有命令:

public class ViewModelBase<T>: Bindable where T: Bindable
{
    private T _child;
    public T Child...

    public Command SearchCommand { get; private set; }
    public Command NewCommand { get; private set; }
    public Command EditCommand { get; private set; }
    public Command ViewCommand { get; private set; }
    public Command DeleteCommand { get; private set; }
}

这些命令需要Action委托来指示执行时将执行的操作(单击Buttons时)。请注意我们如何开始实际定义功能,而我们甚至还没有触及UI的任何部分。

这是您定义操作的方式:

public class ViewModelBase<T>: Bindable where T: Bindable
{
     //... all of the above.

    //in the constructor:
    public ViewModelBase()
    {
        SearchCommand = new Command(Search);
        NewCommand = new Command(New);
        //... And so on...
    }

    //Actions for the commands:
    private void Search()
    {
        //... your search logic here.
    }

    private void New()
    {
        //... your New logic here...
    }

    //... And so on...
}

启用和禁用命令:您还提到第二个标签中会有DataGrid,其中包含搜索结果,这将启用/禁用某些按钮

请注意,Command类定义了IsEnabled属性,而后者又引发了System.Windows.ICommand.CanExecuteChanged事件。 WPF的命令引擎能够监听此事件,并相应地启用/禁用UI元素。因此,在您的应用程序中的任何时候,您都可以切换按钮&#39;通过做:

NewCommand.IsEnabled = false; //disables the "New" Button
DeleteCommand.IsEnabled = true; //enables the "Delete" Button

4 - DataGrid

这是最有趣的部分。

在抽象中,WPF中的DataGridListBoxListViewComboBox和所有ItemsControl是显示集合中项目的控件,最终允许用户选择一个或多个项目。

在WPF中,通常使用ObservableCollection<T>,因为它是一种专门的集合类型,只要在添加/删除/清除项目时就会引发事件。 WPF的绑定引擎侦听此类事件并相应地更新UI。

由于我们不知道DataGrid中将显示哪些类型的项目,并且我们的最终目标是可重用性,现在是时候向我们的父ViewModelBase添加另一个泛型类型参数了:

public class ViewModelBase<T, TItems>: Bindable where T: Bindable where TItems: class
{
   //... All of the above...
}

现在我们可以定义Collection属性:

public class ViewModelBase<T, TItems>: Bindable where T: Bindable where TItems: class
{
   //... All of the above...

    private ObservableCollection<TItems> _searchResults;
    public ObservableCollection<TItems> SearchResults
    {
        get { return _searchResults; }
        private set
        {
            _searchResults = value;
            OnPropertyChanged("SearchResults");
        }
    }

我们还需要一个属性来存储所选项目,这又会导致在选择项目时启用Button,并在清除选择时禁用:

    private TItems _selectedItem;
    public TItems SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;

            NewCommand.IsEnabled = value != null;
            ViewCommand.IsEnabled = value != null;
            DeleteCommand.IsEnabled = value != null;
        }
    }
}

此时,您确实意识到我们实际上在多大程度上编程了UI&#34;甚至没有触摸 UI。上面的代码将在Button中选择项目时启用“新建”,“查看”和“删除DataGrid”,如果未选中则禁用。但是,所有这些功能都与UI完全分离。

5 - 子窗口小部件(TabItem 1)

此时我们有一个完全通用的,可重复使用的基本功能,可以适用于任何项目类型,也允许任何Child&#34;小部件&#34;放在父母的内部。

还要注意这比传统的winforms方法好一千倍,因为:

  • 1 - 可以单元测试。
  • 2 - DataGrid的Items集合实际上是通用的,这意味着您可以获得强类型,编译时可验证的,支持Intellisense的编码(而不仅仅是List<object>等)需要铸造或任何其他糟糕的编码实践。
  • 3 - UI甚至没有定义,这意味着您获得了大量的自定义机会。到目前为止我们编写的代码will work,无论用户界面是否包含DataGrid,还是飞行粉红色大象的3D投影。

现在,只需要创建将放置在30个不同Windows中的每个窗口中的子窗口小部件

例如,假设您需要显示以下内容:

enter image description here

您需要做的第一件事是为此定义ViewModel,如下所示:

public class PersonViewModel: Bindable
{
    public string LastName { get; set; }

    public string FirstName { get; set; }

    public string City { get; set; }

    public string PostalCode { get;set; }
}

请注意,我们不会在此处提出属性更改通知,因为我们并不真正需要双向数据绑定。 OneWay就足够了。我们仍然继承Bindable,因为Parent ViewModel在T类型参数中有一个类型约束。

然后,您需要Create a UserControl

<UserControl x:Class="WpfApplication1.PersonView"
             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" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Label HorizontalAlignment="Right" Grid.Row="0" Grid.Column="0" Content="Last Name:" />
        <Label HorizontalAlignment="Right" Grid.Row="1" Grid.Column="0" Content="First Name:" />
        <Label HorizontalAlignment="Right" Grid.Row="2" Grid.Column="0" Content="City:" />
        <Label HorizontalAlignment="Right" Grid.Row="3" Grid.Column="0" Content="Postal Code:" />

        <TextBox Grid.Row="0" Grid.Column="1" Margin="2" Text="{Binding LastName}"/>
        <TextBox Grid.Row="1" Grid.Column="1" Margin="2" Text="{Binding FirstName}"/>
        <TextBox Grid.Row="2" Grid.Column="1" Margin="2" Text="{Binding City}"/>
        <TextBox Grid.Row="3" Grid.Column="1" Margin="2" Text="{Binding PostalCode}"/>
    </Grid>
</UserControl>

注意你在WPF中实际做的最后一件事是如何创建UI。

6 - DataTemplates

为了让WPF知道&#34;哪个View用于哪个ViewModel&#34;您可以在应用级资源中定义DataTemplates,只需打开App.xaml并在Application.Resources内添加这些资源:

<Application x:Class="WpfApplication1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApplication1"
             StartupUri="MainWindow.xaml">

    <Application.Resources>

        <DataTemplate DataType="{x:Type local:PersonViewModel}">
            <local:PersonView/>
        </DataTemplate>
    </Application.Resources>

请注意,您需要Import your Namespaces才能在XAML中引用您的课程。

好的,我现在必须走了,继续尝试这些步骤。当我有更多时间时,我会完成我的帖子。

答案 1 :(得分:1)

因此,为了回答部分问题,我会尝试将您的工具栏(搜索,新建,编辑..等等)和状态栏解压缩到mainwindow.xaml中。那时你可以做点什么:

具有单独的视图(usercontrols imo),它们将成为主窗口中内容控件的一部分。

<MainWindow>

   <!-- Toolbar -->
   <ContentControl Content="{Binding CurrentTabView}" /> <!-- Where CurrentTabView is of type interface that all your views implement -->

   <!-- Status bar -->

</MainWindow>

视图模型:

Public IView CurrentTabView { get; set; } //All usercontrol viewmodels implement IView

您可能有的问题: 如果我的工具栏在主窗口视图模型中,我如何与其他用于其他视图的视图模型交谈?

我会研究MessageSenders / MessageListeners

的内容
SendMessage(MessageTokens.SearchToken, new NotificationEventArgs<bool>(null, true)); 

SimpleMvvmToolKit有一个很好的Message系统,你的所有视图模型都可以继承。希望这会有所帮助。