我有一个从数据库填充的人员列表,人员对象中的两个字段(id,名称)通过单选按钮进行数据绑定,现在当我单击单选按钮时,我将打开一个新的 UserControl 并尝试显示UserControl 中所选对象的详细信息。 我如何在 mvvm 中实现这个?
人物模型:
namespace Persons.Models
{
class PersonModel
{
public int PersonID { get; set; }
public string Name { get; set; }
public string Surname {get; set; }
public string PersonAge { get; set; }
public string PersonAddress { get; set; }
public string PersonDepartmentID{ get; set; }
public string PersonGroupID{ get; set; }
public string PersonContact{ get; set; }
public string PersonLevelEductationID{ get; set; }
public string PersonDivisionID{get; set; }
public ObservableCollection<PersonModel> Get()
{
ObservableCollection<PersonModel> Person_list = new ObservableCollection<PersonModel>();
var Connect = ConfigurationManager.ConnectionStrings["MysqlConnection"].ConnectionString;
using (var conn = new MySqlConnection(Connect))
using (var command = new MySqlCommand())
{
conn.Open();
command.Connection = conn;
command.CommandText = @"SELECT person.id, person.name, person.surname, person.address, person.age, person.contact department.name AS 'department', group.name AS 'group', eductation.lavel division.name AS 'division' FROM person
LEFT JOIN department ON department.id = person.departmentID LEFT JOIN group ON group.id = person.groupID LEFT JOIN eductation ON eductation.id = person.eductationLavelID LEFT JOIN division ON division.id = person.divisionID";
MySqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Person_list.Add(new PersonModel()
{
PersonID = (int)reader["id"],
Name = (string)reader["name"],
Surname = (string)reader["surname"],
PersonAge = (int)reader["age"],
PersonAddress = (string)reader["address"],
PersonDepartmentID = (string)reader["department"],
PersonGroupID= (string)reader["group"],
PersonContact = (string)reader["contact"],
PersonLevelEductationID = (string)reader["lavel"],
PersonDivisionID = (string)reader["division"]
}
) ;
}
reader.Close();
conn.Close();
}
return Person_list;
}
}
}
查看模型
namespace Persons.ViewModels
{
class PersonViewModel : ViewModelBase
{
private ObservableCollection<PersonModel> _personList;
private readonly PersonModel _person_Model;
public ObservableCollection<PersonModel> PersonItems
{
get => _personList;
set
{
if (_personList!= value)
{
_personList = value;
OnpropertyChanged(nameof(PersonItems));
}
}
}
public PersonViewModel()
{
_person_Model= new PersonModel();
_personList= _person_Model.Get();
}
}
}
主要用户控制:
<ScrollViewer
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding PersonItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<RadioButton
Content="{Binding Name}"
CommandParameter="{Binding Name}"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ShowPageCommand}"
Style="{StaticResource ItemsButtonStyle}"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
和新的用户控件
<ScrollViewer
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding PersonItems}" BorderBrush="White">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Width="600"
MaxHeight="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock
HorizontalAlignment="Left"
Grid.Row="1"
Grid.Column="0"
Text="Name:" />
<TextBlock
HorizontalAlignment="Right"
Grid.Row="1"
Grid.Column="0"
Text="{Binding Name}" />
<TextBlock
HorizontalAlignment="Left"
Grid.Row="2"
Text="Surname :" />
<TextBlock
HorizontalAlignment="Right"
Grid.Row="2"
Text="{Binding Surname}"/>
<TextBlock
HorizontalAlignment="Left"
Grid.Row="3"
Text="PersonAge :" />
<TextBlock
HorizontalAlignment="Right"
Grid.Row="3"
Text="{Binding PersonAge }"/>
<TextBlock
HorizontalAlignment="Left"
Grid.Row="4"
Text="Person Address:" />
<TextBlock
HorizontalAlignment="Right"
Grid.Row="4"
Text="{Binding PersonAddress }"/>
<TextBlock
HorizontalAlignment="Left"
Grid.Row="6"
Text="Department:" />
<TextBlock
HorizontalAlignment="Right"
Grid.Row="6"
Text="{Binding PersonDepartmentID}"/>
........
<TextBlock
HorizontalAlignment="Left"
Grid.Row="9"
Text="Eductation :" />
<TextBlock
HorizontalAlignment="Right"
Grid.Row="9"
Text="{Binding PersonLevelEductationID}"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
和命令
namespace Persons.ViewModels
{
class NavigationViewModel : INotifyPropertyChanged
{
public NavigationViewModel()
{
// Set Startup Page
SelectedViewModel = new StartupViewModel();
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
// Select ViewModel
private object _selectedViewModel;
public object SelectedViewModel
{
get => _selectedViewModel;
set { _selectedViewModel = value; OnPropertyChanged("SelectedViewModel"); }
}
public void CrudView()
{
SelectedViewModel = new CrudPage(); // new usercontrol
}
private ICommand _CrudCommand;
public ICommand ShowPageCommand
{
get
{
if (_CrudCommand == null)
{
_CrudCommand = new RelayCommand(param => CrudView());
}
return _CrudCommand;
}
}
// Close App
public void CloseApp(object obj)
{
MainWindow win = obj as MainWindow;
win.Close();
}
// Close App Command
private ICommand _closeCommand;
public ICommand CloseAppCommand
{
get
{
if (_closeCommand == null)
{
_closeCommand = new RelayCommand(p => CloseApp(p));
}
return _closeCommand;
}
}
}
RelayCommand类
namespace Persons.Utilities
{
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public RelayCommand(Action<object> execute)
{
this.execute = execute;
canExecute = null;
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return canExecute == null || CanExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
}
对于长代码,我深表歉意!
您所追求的称为主从视图。您可以通过使用
ContentControl
显示详细信息视图来实现此目的。ContentControl.Content
属性,并定义 DatatTemplate
来实际渲染视图。您可以将此模板分配给 ContentControl.Contenttemplate
属性或将 DataTemplate
定义为隐式,以便自动加载。
由于加载是由
Button
触发的,因此您可以将详细信息模型作为命令参数传递,或者将 ListBox.SelectedItem
属性绑定到也定义 ICommand
的视图模型。
一些注意事项:
PersonModel
不应访问数据库来生成自己的列表。相反,拥有 PersonViewModel
的视图模型必须调用应用程序模型以返回 PersonModel
集合。该模型在内部使用数据库或从文件或服务读取/写入文件或服务。UserControl
设置了错误的 DataContext
。此外,您可以避免显式设置 DataContext,而是让它被继承。UserControl
不应将其内部结构直接绑定到 DataContext
。相反,引入内部可以绑定的依赖属性。然后将这些依赖属性绑定到实际的 DataContext
。如果您想在不同的地方或应用程序中重用该控件,这一点尤其重要。如果您不需要这种灵活性,您当然可以继续将依赖项硬编码到自定义控件中。我只是指出这不是最佳实践。MainViewModel
来公开其他视图模型类,例如 PersonViewModel
或 NavigationViewModel
。然后将 MainViewModel
分配给 DataContext
根(例如 MainWindow
)。这更容易维护并增加灵活性。例如,这样的结构允许您使用依赖项注入,因为唯一要注入的视图模型是将 MainViewModel
注入 MainWindow
。Button.Click
事件或使用路由命令和 Button.Command
属性。IDisposable
的资源。 using
语句将为您做到这一点。处理对象将自动关闭它们并刷新缓冲区,例如如果有 Stream
物体。MainUserControl.xaml
<UserControl>
<!-- Bind to the nested PersonViewModel -->
<ListBox ItemsSource="{Binding PersonViewModel.PersonItems}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type PersonModel}">
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<!-- Bind the Command to the nested NavigationViewModel -->
<RadioButton Content="{Binding Name}"
CommandParameter="{Binding}"
Command="{Binding NavigationViewModel.ShowPageCommand}"
Style="{StaticResource ItemsButtonStyle}" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
PersonModelDetailsView.xaml
<UserControl x:Name="Root">
<ContentControl Content="{Binding ElementName=Root, Path=PersonModel}"
ContentTemplate="{Binding ElementName=Root, Path=PersonModelTemplate}" />
</UserControl>
PersonModelDetailsView.xaml.cs
partial class PersonModelDetailsView : UserControl
{
public PersonModel PersonModel
{
get => (PersonModel)GetValue(PersonModelProperty);
set => SetValue(PersonModelProperty, value);
}
public static readonly DependencyProperty PersonModelProperty = DependencyProperty.Register(
nameof(PersonModel),
typeof(PersonModel),
typeof(PersonModelDetailsView),
new FrameworkPropertyMetadata(default));
public DataTemplate PersonModelTemplate
{
get => (DataTemplate)GetValue(PersonModelTemplateProperty);
set => SetValue(PersonModelTemplateProperty, value);
}
public static readonly DependencyProperty PersonModelTemplateProperty = DependencyProperty.Register(
nameof(PersonModelTemplate),
typeof(DataTemplate),
typeof(PersonModelDetailsView),
new FrameworkPropertyMetadata(default));
}
MainWindow.xaml
<Window>
<Window.DataContext>
<MainViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="PersonModelDetailsViewTemplate"
DataType="{x:Type PersonModel}">
<Border Width="600"
MaxHeight="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Left"
Grid.Row="1"
Grid.Column="0"
Text="Name:" />
<TextBlock HorizontalAlignment="Right"
Grid.Row="1"
Grid.Column="0"
Text="{Binding Name}" />
<TextBlock HorizontalAlignment="Left"
Grid.Row="2"
Text="Surname :" />
<TextBlock HorizontalAlignment="Right"
Grid.Row="2"
Text="{Binding Surname}" />
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <!-- Master view -->
<ColumnDefinition /> <!-- Details view -->
<Grid.ColumnDefinitions>
<MainUserControl Grid.Column="0" />
<PersonModelDetailsView Grid.Column="1"
PersonModel="{Binding NavigationViewModel.SelectedPersonModel}"
PersonModelTemplate="{StaticResource PersonModelDetailsViewTemplate}" />
</Grid>
</Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
private readonly MainViewModel MainViewModel;
public MainWindow()
{
this.MainViewModel = new MainViewModel();
this.DataContext = this.MainViewModel;
InitializeComponent();
// Initialize the view model classes
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// Initializing all view models from the loaded event is a significant performnace improvement.
// Just think about doing all of this work from the constructor
// where each view model class instantiates its own classes that
// themself create instances from their constructor and initialize
// them. It would take "ages" for the construction of the top-level
// instance to complete, while the UI remains unresponsive.
// You can even call async methods which isn't possible in the
// constructor.
// Every class must perform expensive/long-running initialization
// outside the constructor.
this.MainViewModel.Initialize();
}
// TODO::Implement window close and application close logic.
// The logic that was originally implemented in the view model class.
// For example use event handlers or routed events or routed commands.
}
MainViewModel.cs
class MainViewModel : INotifyPropertyChanged
{
public PersonViewModel PersonViewModel { get; } = new PersonViewModel();
public NavigationViewModel NavigationViewModel { get; } = new NavigationViewModel();
// Can be async if required
public void Initialize()
{
// Do not make such potentially long-running and blocking calls from the constructor
this.PersonViewModel.Initialize();
}
}
PersonViewModel.cs
class PersonViewModel : ViewModelBase
{
public ObservableCollection<PersonModel> PersonItems { get; }
// The model class to access the database
private readonly DataRepository dataRepository;
public PersonViewModel()
{
this.PersonItems = new ObservableCollection<PersonModel>();
}
// Can be async if required
public void Initialize()
{
// Do not make such potentially long-running and blocking calls from the constructor
IEnumerable<PersonModel> personModels = this.dataRepository.GetPersonModels();
foreach (PersonModel personModel in personModels)
{
this.PersonItems.Add(personModel);
}
}
}
NavigationViewModel.cs
class NavigationViewModel : INotifyPropertyChanged
{
public NavigationViewModel()
{
// Set Startup Page
SelectedViewModel = new StartupViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
// Select ViewModel
private object _selectedViewModel;
public object SelectedViewModel
{
get => _selectedViewModel;
set { _selectedViewModel = value; OnPropertyChanged(nameof(this.SelectedViewModel)); }
}
// Selected PersonModel
private PersonModel _selectedPersonModel;
public PersonModel SelectedPersonModel
{
get => _selectedPersonModel;
set { _selectedPersonModel = value; OnPropertyChanged(nameof(this.SelectedPersonModel)); }
}
public void ExecuteShowPageCommand(object commandParameter)
{
if (commandParameter is not PersonModel detailsPersonModel)
{
return;
}
// Use data models to navigate.
// Creating controls in the view model class (like you were doing)
// is forbidden in MVVM!
this.SelectedPersonModel = detailsPersonModel;
}
private ICommand _CrudCommand;
public ICommand ShowPageCommand
{
get
{
if (_CrudCommand == null)
{
_CrudCommand = new RelayCommand(ExecuteShowPageCommand);
}
return _CrudCommand;
}
}
/*
* The following code does not belong into a view model class.
* This must be handled by the view! For example in code-behind.
* You can use event handlers for Button.Click events or routed commands.
* Then close the Window or the application from there.
* Handling controls in the view model violates the MVVM design pattern.
*/
//// Close App
//public void CloseApp(object obj)
//{
// MainWindow win = obj as MainWindow;
// win.Close();
//}
//// Close App Command
//private ICommand _closeCommand;
//public ICommand CloseAppCommand
//{
// get
// {
// if (_closeCommand == null)
// {
// _closeCommand = new RelayCommand(p => CloseApp(p));
// }
// return _closeCommand;
// }
//}
}
DataRepository.cs
模型类。
数据持久性(数据库和文件 I/O 或服务消耗)始终属于应用程序模型。而不是视图模型或数据模型类。旨在将所有查询放在一个位置(以避免重复代码)。
class DataRepository
{
public IEnumerable<PersonModel> GetPersonModels()
{
var persons = new List<PersonModel>();
var Connect = ConfigurationManager.ConnectionStrings["MysqlConnection"].ConnectionString;
using (var conn = new MySqlConnection(Connect))
using (var command = new MySqlCommand())
{
conn.Open();
command.Connection = conn;
command.CommandText = @"SELECT person.id, person.name, person.surname, person.address, person.age, person.contact department.name AS 'department', group.name AS 'group', eductation.lavel division.name AS 'division' FROM person
LEFT JOIN department ON department.id = person.departmentID LEFT JOIN group ON group.id = person.groupID LEFT JOIN eductation ON eductation.id = person.eductationLavelID LEFT JOIN division ON division.id = person.divisionID";
using MySqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
Person_list.Add(new PersonModel()
{
PersonID = (int)reader["id"],
Name = (string)reader["name"],
Surname = (string)reader["surname"],
PersonAge = (int)reader["age"],
PersonAddress = (string)reader["address"],
PersonDepartmentID = (string)reader["department"],
PersonGroupID = (string)reader["group"],
PersonContact = (string)reader["contact"],
PersonLevelEductationID = (string)reader["lavel"],
PersonDivisionID = (string)reader["division"]
});
}
}
// Don't close these IDisposables. The using-block will handle that.
//reader. Close();
//conn.Close();
return persons;
}
}