Luc*_*Dev 4 wpf controltemplate
我正在努力了解和学习更多关于WPF的知识.
在具有30个窗口的WPF项目中,所有窗口必须具有:
"逻辑顺序"是:
只有在选择了gridview中的行时,才能启用按钮new/edit/view和delete."逻辑顺序"对所有窗口都有效!
在winform中,您可以创建一个表单,其中包含用作模板的结构,并创建30个从模型继承的窗口.通过模板中的自定义事件,您可以了解用户是否在gridview中选择了一行,了解用户是否单击工具栏上的按钮等
例如,当用户点击New按钮生成"NewItem"事件时,该事件是自定义事件,然后在返回"NewItem"事件时从模型继承的形式打开输入表单


你可以在WPF中做类似的事情吗?
您可以在WPF中创建表单模板并创建从模板继承的窗口吗?
我希望我很清楚并抱歉我的英语不好
谢谢
好的,我会尝试提供一个详细的答案,所以这可能会有点长,但请耐心等待.
首先,如果你正在使用WPF,那么从winforms这样的"传统"技术中留下你可能习惯的任何东西是非常重要的,相反,理解并接受WPF心态.
在WPF中,您不"从基础窗口继承"以定义应用程序的功能,因为UI不是应用程序.UI只是让最终用户与应用程序交互的好方法.
相反,应用程序的交互逻辑和功能体现在一些名为ViewModels的类中,它们基本上定义了UI要公开的所有内容和操作.
之后,UI通过DataBinding "连接"到ViewModel .
这允许极高水平的可重用性,可伸缩性,可维护性甚至可测试性,因为它实际上完全将UI与应用程序/业务逻辑和数据分开.
因此,这些是在WPF中执行您所描述的内容所需的基本步骤:
由于WPF中的双向数据绑定需要属性更改通知,我们首先要做的是创建一个基本的"可绑定"类来支持这个基本功能:
public class Bindable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Run Code Online (Sandbox Code Playgroud)
System.ComponentModel.INotifyPropertyChanged它是基本的System.dll.Net Framework程序集的一部分, 而不是WPF特定的.您的应用程序将要求在基类中定义父窗口功能,同时留下继承和自定义每个特定窗口类型的"子"部分的机会,因此我们将创建基本通用ViewModel来定义所有常见的可重用功能,同时还定义了一个通用的"Child"属性:
public class ViewModelBase<T>: Bindable where T: Bindable
{
private T _child;
public T Child
{
get { return _child; }
set
{
_child = value;
OnPropertyChanged("Child");
}
}
}
Run Code Online (Sandbox Code Playgroud)

OnPropertyChanged()在属性设置器中引发该属性.之后,您需要定义工具栏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;
}
}
Run Code Online (Sandbox Code Playgroud)
使用此命令类,我们现在可以在主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; }
}
Run Code Online (Sandbox Code Playgroud)
这些命令需要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...
}
Run Code Online (Sandbox Code Playgroud)
启用和禁用命令:您还提到DataGrid第二个选项卡中将有一个包含搜索结果的选项卡,它将启用/禁用某些按钮.
请注意,Command类定义和IsEnabled属性,后者又引发System.Windows.ICommand.CanExecuteChanged事件.WPF的命令引擎能够监听此事件,并相应地启用/禁用UI元素.因此,在应用程序中的任何时候,您都可以通过执行以下操作来切换按钮的状态:
NewCommand.IsEnabled = false; //disables the "New" Button
DeleteCommand.IsEnabled = true; //enables the "Delete" Button
Run Code Online (Sandbox Code Playgroud)
这是最有趣的部分.
在抽象DataGrid,ListBox,ListView, ComboBox,和所有ItemsControlS IN WPF是从集合中显示项目,最终使用户可以选择一个或多个项目的控件.
在WPF中,通常使用ObservableCollection<T>,因为它是一种专门的集合类型,只要在添加/删除/清除项目时就会引发事件.WPF的绑定引擎侦听此类事件并相应地更新UI.
由于我们不知道DataGrid中将显示哪种类型的项目,并且我们的最终目标是可重用性,现在是时候向我们的父级添加另一个泛型类型参数ViewModelBase:
public class ViewModelBase<T, TItems>: Bindable where T: Bindable where TItems: class
{
//... All of the above...
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以定义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");
}
}
Run Code Online (Sandbox Code Playgroud)
我们还需要一个属性来存储所选项目,这反过来将导致在选择项目Button时启用s,并在清除选择时禁用:
private TItems _selectedItem;
public TItems SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
NewCommand.IsEnabled = value != null;
ViewCommand.IsEnabled = value != null;
DeleteCommand.IsEnabled = value != null;
}
}
}
Run Code Online (Sandbox Code Playgroud)
在这一点上,你确实意识到我们实际上"编程UI"的程度甚至没有触及 UI.上面的代码将在Button选择项目时启用"新建","查看"和"删除",如果未选中则DataGrid禁用.但是,所有这些功能都与UI完全分离.
此时,我们有一个完全通用的,可重用的基本功能,可以适用于任何Item类型,也允许任何Child"widget"放在父类中.
还要注意这比传统的winforms方法好一千倍,因为:
List<object>等等),而无需进行转换或任何其他不良编码实践.will work,无论UI是否包含DataGrid飞行粉红色大象的3D投影.现在,只需要创建将放置在30个不同Windows中的每个子窗口小部件的子窗口小部件
比如说,你需要展示这样的东西:

你需要做的第一件事是定义ViewModelfor,这将是这样的:
public class PersonViewModel: Bindable
{
public string LastName { get; set; }
public string FirstName { get; set; }
public string City { get; set; }
public string PostalCode { get;set; }
}
Run Code Online (Sandbox Code Playgroud)
请注意,我们不会在此处提出属性更改通知,因为我们并不真正需要双向数据绑定.OneWay就足够了.仍然,我们继承,Bindable因为Parent ViewModel在Ttype参数中有一个类型约束.
然后,您需要创建一个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>
Run Code Online (Sandbox Code Playgroud)
注意你在WPF中实际做的最后一件事是如何创建UI.
为了让WPF知道"哪个View用于哪个ViewModel",您可以在应用程序级资源中定义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>
Run Code Online (Sandbox Code Playgroud)
请注意,您需要导入命名空间才能在XAML中引用您的类.
好的,我现在必须走了,继续尝试这些步骤.当我得到更多时间时,我会完成我的帖子.
因此,为了回答您的部分问题,我将尝试将您的工具栏(搜索、新建、编辑等)和状态栏提取到 mainwindow.xaml 中。那时也许你可以做类似的事情:
拥有单独的视图(我认为是用户控件),它们将成为主窗口中内容控件的一部分。
<MainWindow>
<!-- Toolbar -->
<ContentControl Content="{Binding CurrentTabView}" /> <!-- Where CurrentTabView is of type interface that all your views implement -->
<!-- Status bar -->
</MainWindow>
Run Code Online (Sandbox Code Playgroud)
视图模型:
Public IView CurrentTabView { get; set; } //All usercontrol viewmodels implement IView
Run Code Online (Sandbox Code Playgroud)
您可能有一个问题:如果我的工具栏位于主窗口视图模型中,我如何与用于其他视图的其他视图模型对话?
我会研究一些类似于 MessageSenders/MessageListeners 的东西
SendMessage(MessageTokens.SearchToken, new NotificationEventArgs<bool>(null, true));
Run Code Online (Sandbox Code Playgroud)
SimpleMvvmToolKit 有一个很好的消息系统,所有视图模型都可以继承。希望这能有所帮助。