我正在努力了解并学习更多关于WPF的知识。
在具有30个窗口的WPF项目中,所有窗口必须具有:
“逻辑顺序”是:
只有在选择了gridview中的行时,才能启用按钮new / edit / view和delete。 “逻辑顺序”对所有窗口都有效!
在winform中,您可以创建一个表单,其中包含用作模板的结构,并创建30个从模型继承的窗口。 通过模板中的自定义事件,您可以了解用户是否在gridview中选择了一行,了解用户是否单击工具栏上的按钮等
例如,当用户点击New按钮时会生成“NewItem”事件,这是一个自定义事件,然后以表格继承模型时重新启动“NewItem”事件打开输入表单
你可以在WPF中做类似的事情吗?
您可以在WPF中创建表单模板并创建从模板继承的窗口吗?
我希望我很清楚并抱歉我的英语不好
谢谢
答案 0 :(得分:8)
好的,我会尝试提供一个详细的答案,所以这可能会有点长,但请耐心等待。
首先,如果你正在使用WPF,那么留下你可能习惯的任何东西是非常重要的。传统的"诸如winforms之类的技术,相反,理解并拥抱The WPF Mentality。
在WPF中,您不会"从基础窗口" 继承以定义应用程序的功能,因为 UI是不是应用程序。 UI只是让最终用户与应用程序交互的好方法。
相反,应用程序的交互逻辑和功能体现在一些名为ViewModels的类中,它基本上定义了UI要公开的所有内容和操作。
之后,用户界面已连接"通过DataBinding到ViewModel。
这样可以实现高水平的可重用性,可伸缩性,可维护性甚至可测试性,因为它实际上完全将UI与应用程序/业务逻辑和数据分开。
因此,这些是在WPF中执行您所描述的内容所需的基本步骤:
由于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));
}
}
System.ComponentModel.INotifyPropertyChanged
,它是基本的System.dll
.Net Framework程序集的一部分,并不是特定于WPF的。您的应用程序将要求在基类中定义父窗口功能,同时留下继承和自定义" 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");
}
}
}
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;
}
}
使用此命令类,我们现在可以在主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
这是最有趣的部分。
在抽象中,WPF中的DataGrid
,ListBox
,ListView
,ComboBox
和所有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完全分离。
此时我们有一个完全通用的,可重复使用的基本功能,可以适用于任何项目类型,也允许任何Child&#34;小部件&#34;放在父母的内部。
还要注意这比传统的winforms方法好一千倍,因为:
List<object>
等)需要铸造或任何其他糟糕的编码实践。will work
,无论用户界面是否包含DataGrid
,还是飞行粉红色大象的3D投影。 现在,只需要创建将放置在30个不同Windows中的每个窗口中的子窗口小部件
例如,假设您需要显示以下内容:
您需要做的第一件事是为此定义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。
为了让WPF知道&#34;哪个View用于哪个ViewModel&#34;您可以在应用级资源中定义DataTemplate
s,只需打开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系统,你的所有视图模型都可以继承。希望这会有所帮助。