一、Entity Framework Core
特点:【跨平台】,【建模】,【查询、更改、保存】,【并发】,【事务】,【缓存】,【数据迁移】
EF的组件
二、重构:构建数据模型
项目延续C#学习(九)的 项目代码,以此基础进行进一步重构
所需的NuGet包如下:
逆向数据库获得数据模型(Model)
首先在根目录下创建Models文件夹,然后使用Tools->NuGet包管理器->程序包管理器控制台
输入指令
Scaffold-DbContext "自己的数据库连接字符串" Microsoft.EntityFrameworkCore.Sqlserver -OutputDir Models -Context AppDbContext
处理完成后,就可以在Models文件夹中看到通过逆向数据库构建的数据模型啦!
三、OMR数据管理
使用Entity Framework 取代SQL语句
使用ORM来自动生成SQL语句,通过数据库的映射框架获取数据模型,通过模型框架的链式结构来处理数据,可以使业务实现在代码中,而不是实现在SQL语句中。因此对于程序员来说,使用对象的链式结构更加符合面向对象的编程理念。
通过数据模型向UI传递和绑定数据
代码改进后如下:
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ShowCustomers();
}
//访问数据库
private void ShowCustomers()
{
try
{
using(var db = new AppDbContext())
{
var customers = db.Customers.ToList();
customerList.DisplayMemberPath = "Name";
customerList.SelectedValuePath = "Id";
customerList.ItemsSource = customers;
}
}catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void customerList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
Customer selectedItem = customerList.SelectedItem as Customer;
if (selectedItem == null)
{
appointmentList.ItemsSource = null;
return;
}
NameTextBox.Text = selectedItem.Name;
IdTextBox.Text = selectedItem.IdNumber;
AddressTextBox.Text = selectedItem.Address;
using(var db = new AppDbContext())
{
var customerId = customerList.SelectedValue;
var appointment = db.Appointments
.Where(a => a.CustomerId == (int)customerId)
.ToList();
appointmentList.DisplayMemberPath = "Time";
appointmentList.SelectedValuePath = "Id";
appointmentList.ItemsSource = appointment;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void CancelAppointment_Click(object sender, RoutedEventArgs e)
{
try
{
var appointmentId = appointmentList.SelectedValue;
using (var db = new AppDbContext())
{
var appointmentToRemove = db.Appointments
.Where(a => a.Id == (int)appointmentId)
.FirstOrDefault();//因为Id主键是唯一选择,因此这里过滤后不再是列表,而是独立的对象,因此使用FirstOrDefault
db.Appointments.Remove(appointmentToRemove);
db.SaveChanges();
}
MessageBox.Show("取消预约成功!");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
customerList_SelectionChanged(null, null);
}
}
private void DeleteCustomer_Click(object sender, RoutedEventArgs e)
{
try
{
var customerId = customerList.SelectedValue;
using(var db = new AppDbContext())
{
//使用Entity Framework后,不需要进行两次数据库操作,只需要使用Include方法
var customerToRemove = db.Customers
//.Include(c => c.Appointments)
.Where(c => c.Id == (int)customerId)
.Include(c => c.Appointments)
.FirstOrDefault();
db.Customers.Remove(customerToRemove);
db.SaveChanges();
}
MessageBox.Show("删除用户成功!");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
ShowCustomers();
customerList_SelectionChanged(null, null);
}
}
private void AddCustomer_Click(object sender, RoutedEventArgs e)
{
try
{
using (var db = new AppDbContext())
{
var customer = new Customer()
{
Name = NameTextBox.Text,
IdNumber = IdTextBox.Text,
Address = AddressTextBox.Text
};
db.Customers.Add(customer);
db.SaveChanges();
}
MessageBox.Show("添加用户信息成功!");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
ShowCustomers();
}
}
private void AddAppointment_Click(object sender, RoutedEventArgs e)
{
try
{
using(var db = new AppDbContext())
{
var appointment = new Appointment()
{
Time = DateTime.Parse(AppointmentDatePicker.Text),
CustomerId = (int)customerList.SelectedValue
};
db.Appointments.Add(appointment);
db.SaveChanges();
}
MessageBox.Show("预约成功!");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
customerList_SelectionChanged(null, null);
}
}
private void UpdateCustomer_Click(object sender, RoutedEventArgs e)
{
try
{
using (var db = new AppDbContext())
{
var customer = db.Customers.Where(c => c.Id == (int)customerList.SelectedValue).FirstOrDefault();
customer.Name = NameTextBox.Text.Trim();
customer.IdNumber = IdTextBox.Text.Trim();
customer.Address = AddressTextBox.Text.Trim();
db.SaveChanges();
}
MessageBox.Show("预约成功!");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
ShowCustomers();
}
}
}
易报错点提示
1.联级删除时,会出现appointment表的customerId为空情况,因此需要再customerList_SelectionChanged方法里进行一个判空处理;
2.使用联级删除,使用Entity Framework后,不需要进行两次数据库操作,只需要使用Include方法,但需要引入using Microsoft.EntityFrameworkCore;
3.注意生成的AppDbContext.cs文件中,OnModelCreating方法中的OnDelete的DeleteBehavior,使用Cascade方法,Automatically deletes dependent entities when the principal is deleted or the
relationship to the principal is severed, but creates a non-cascading foreign key constraint in the database..OnDelete(DeleteBehavior.Cascade)
四、布局重构
首先对于原来丑陋的展示页面进行重新布局,构建我们的基础布局框架
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="240"/>
<ColumnDefinition Width="280"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!--header-->
<Border Grid.ColumnSpan="3" Background="#9a0070">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<!--随意添加一个图片,图片放置在根目录文件夹下的Images文件中-->
<Image Height="90" Margin="5" Source="/Images/logo.jpg"/>
<TextBlock Text="WPF客户管理系统" FontSize="40" VerticalAlignment="Center" Foreground="#ffffff"/>
</StackPanel>
</Border>
<StackPanel Grid.Row="1" Grid.Column="0">
<Button Content="添加客户"/>
<ListView/>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1">
<TextBlock Text="姓名" Margin="10 10 10 0"/>
<TextBox Margin="10"/>
<TextBlock Text="身份证号" Margin="10 10 10 0"/>
<TextBox Margin="10"/>
<TextBlock Text="地址" Margin="10 10 10 0"/>
<TextBox Margin="10"/>
<Button Content="保存" Margin="10 10 10 30" VerticalAlignment="Bottom" HorizontalAlignment="Left"/>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="2">
<ListView/>
<TextBlock Text="添加新预约"/>
<DatePicker Margin="10"/>
<Button Content="预约"/>
</StackPanel>
</Grid>
完成布局的组件化控制
首先在根目录下创建文件夹“Control”,在文件夹中新建项“用户控件(WPF)”命名HeaderControl.xaml,将MainWindow.xaml中的header代码转移至此
HeaderControl.xaml
<Border Grid.ColumnSpan="3" Background="#9a0070">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Image Height="90" Margin="5" Source="/Images/logo.jpg"/>
<TextBlock Text="WPF客户管理系统" FontSize="40" VerticalAlignment="Center" Foreground="#ffffff"/>
</StackPanel>
</Border>
MainWindow.xaml中对应部分删除,替换为
<!--header-->
<controls:HeaderControl Grid.ColumnSpan="3"/>
五、MVVM架构
MVVM指☞Model(模型) View(视图) ViewModel(视图模型)
直接使用View也可以进行项目的开发,正如上一篇文章所示例的,但是直接使用View访问数据库,无法完成数据的隔离,无法进行复杂的业务开发,甚至无法可持续的维护系统,因此必须进行业务与数据的隔离,以及业务与界面的隔离。因此对于业务进行分离后,就得到了视图模型,视图模型可以全部或者部分使用模型的字段,模型的字段通过映射的方式向视图模型提供业务的支持,而视图模型与视图则双向绑定,不仅可以让用户看到数据,还可以通过UI交互操作数据,而视图模型作为业务的载体,也会承担与数据库的沟通工作,视图模型会处理一切与UI的交互行为。
MVVM的优点
- [ 兼容MVC架构 ]
- [ 业务与UI逻辑彻底分开,方便测试 ]
- [ 方便维护 ]
MVVM的缺点
- [ 代码量增加 ]
- [ 对象调用复杂度增加 ]
MVVM项目代码重构
首先,在根目录下创建文件夹ViewModels,新建项目MainViewModel,CustomerViewModel,AppointmentViewModel
代码如下:
MainWindow.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="240"/>
<ColumnDefinition Width="280"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!--header-->
<controls:HeaderControl Grid.ColumnSpan="3"/>
<StackPanel Grid.Row="1" Grid.Column="0">
<Button Content="添加客户" Click="ClearSelectedCustomer_Click"/>
<ListView ItemsSource="{Binding Customers, Mode=OneWay}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedCustomer, Mode=TwoWay}"/>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="1">
<TextBlock Text="姓名" Margin="10 10 10 0"/>
<TextBox x:Name="NameTextBox" Margin="10" Text="{Binding SelectedCustomer.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="身份证号" Margin="10 10 10 0"/>
<TextBox Name="IdTextBox" Margin="10" Text="{Binding SelectedCustomer.IdNumber, Mode=TwoWay}"/>
<TextBlock Text="地址" Margin="10 10 10 0"/>
<TextBox x:Name="AddressTextBox" Margin="10" Text="{Binding SelectedCustomer.Address, Mode=TwoWay}"/>
<Button Content="保存" Margin="10 10 10 30" VerticalAlignment="Bottom" HorizontalAlignment="Left" Click="SaveCustomer_Click"/>
</StackPanel>
<StackPanel Grid.Row="1" Grid.Column="2">
<ListView ItemsSource="{Binding Appointments, Mode=TwoWay}" DisplayMemberPath="Time"/>
<TextBlock Text="添加新预约"/>
<DatePicker Name="AppointmentDatePicker" Margin="10"/>
<Button Content="预约" Click="AddAppointment_Click"/>
</StackPanel>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private MainViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainViewModel();
_viewModel.LoadCustomers();
DataContext = _viewModel;
}
private void ClearSelectedCustomer_Click(object sender, RoutedEventArgs e)
{
_viewModel.ClearSelectedCustomer();
}
private void SaveCustomer_Click(object sender, RoutedEventArgs e)
{
try
{
string name = NameTextBox.Text.Trim();
string idNumber = IdTextBox.Text.Trim();
string address = AddressTextBox.Text.Trim();
_viewModel.SaveCustomer(name, idNumber, address);
}catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void AddAppointment_Click(object sender, RoutedEventArgs e)
{
try
{
DateTime time = DateTime.Parse(AppointmentDatePicker.Text);
_viewModel.AddAppointment(time);
}catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
AppointmentViewModel.cs
public class AppointmentViewModel
{
private Appointment _appointment;
public AppointmentViewModel(Appointment appointment)
{
_appointment = appointment;
}
//因为Id为只读属性,因此不需要set
public int Id { get => _appointment.Id; }
public DateTime Time { get => _appointment.Time; set
{
//有且仅当数据发生变化,才向数据库写入数据
if(value != _appointment.Time)
{
_appointment.Time = value;
}
}
}
}
CustomerViewModel.cs
public class CustomerViewModel
{
private Customer _customer;
public CustomerViewModel(Customer customer)
{
_customer = customer;
}
public int Id { get => _customer.Id; }
public string Name { get => _customer.Name; set
{
if(_customer.Name != value)
{
_customer.Name = value;
}
}
}public string IdNumber { get => _customer.IdNumber; set
{
if(_customer.IdNumber != value)
{
_customer.IdNumber = value;
}
}
}public string Address { get => _customer.Address; set
{
if(_customer.Address != value)
{
_customer.Address = value;
}
}
}
}
MainViewModel.cs
//INotifyPropertyChanged刷新更新的属性
public class MainViewModel : INotifyPropertyChanged
{
//初始化空列表避免程序运行过程中出现为止的内存问题
//使用观察者模式ObservableCollection实时更新添加后的客户数据
public ObservableCollection<CustomerViewModel> Customers { get; set; } = new();
public ObservableCollection<AppointmentViewModel> Appointments { get; set; } = new();
private CustomerViewModel _selectedCustomer;
public event PropertyChangedEventHandler? PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public CustomerViewModel SelectedCustomer
{
get => _selectedCustomer;
set
{
if(value != _selectedCustomer)
{
_selectedCustomer = value;
RaisePropertyChanged(nameof(SelectedCustomer));
LoadAppointments(SelectedCustomer.Id);
}
}
}
public void LoadCustomers()
{
Customers.Clear();//重置客户列表
using (var db = new AppDbContext())
{
var customers = db.Customers.ToList();
foreach (var customer in customers)
{
Customers.Add(new CustomerViewModel(customer));
}
}
}
public void ClearSelectedCustomer()
{
_selectedCustomer = null;
RaisePropertyChanged(nameof(SelectedCustomer));
}
public void SaveCustomer(string name, string idNumber, string address)
{
if(SelectedCustomer != null)
{
//更新客户数据
using (var db = new AppDbContext())
{
var customer = db.Customers.Where(c => c.Id == SelectedCustomer.Id).FirstOrDefault();
customer.Name = name;
customer.IdNumber = idNumber;
customer.Address = address;
db.SaveChanges();
}
}
else
{
//添加新客户
using (var db = new AppDbContext())
{
var newCustomer = new Customer()
{
Name = name,
IdNumber = idNumber,
Address = address
};
db.Customers.Add(newCustomer);
db.SaveChanges();
}
LoadCustomers();
}
}
public void LoadAppointments(int customerId)
{
Appointments.Clear();
using (var db = new AppDbContext())
{
var appointments = db.Appointments.Where(a => a.CustomerId == customerId).ToList();
foreach(var a in appointments)
{
Appointments.Add(new AppointmentViewModel(a));
}
}
}
public void AddAppointment(DateTime selectedDate)
{
if(SelectedCustomer == null) { return; }
using (var db = new AppDbContext())
{
var newAppointment = new Appointment()
{
Time = selectedDate,
CustomerId = SelectedCustomer.Id
};
db.Appointments.Add(newAppointment);
db.SaveChanges();
}
LoadAppointments(SelectedCustomer.Id);
}
}
六、Material UI框架
安装Material UI框架
接着访问Material Design Themes的项目URL,可以看到对于此框架的使用讲解,将示例中的想使用颜色模式的代码部分,复制粘贴到App.xaml文件中,即可应用
示例代码:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Light" PrimaryColor="DeepPurple" SecondaryColor="Lime" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
在进一步优化中,我们想要实现在日历上显示预约,对于已经有预约的日期,不可在预约这样的效果。想到可以使用BlackoutDates,然而BlackoutDates不支持数据的绑定,也就是无法传入数据,因此需要使用其他方法进行。
这里借用作大神的方法进行操作,完美解决我们的需求!
根目录创建文件夹AttachedProperties,创建文件CalendarAttachedProperties
在大神基础上对于我们的项目略加调整------原答案地址
CalendarAttachedProperties.cs
// Adds a collection of command bindings to a date picker's existing BlackoutDates collection, since the collections are immutable and can't be bound to otherwise.
// Usage: <DatePicker hacks:AttachedProperties.RegisterBlackoutDates="{Binding BlackoutDates}" >
public class CalendarAttachedProperties : DependencyObject
{
#region Attributes
private static readonly List<Calendar> _calendars = new List<Calendar>();
private static readonly List<DatePicker> _datePickers = new List<DatePicker>();
#endregion
#region Dependency Properties
public static DependencyProperty RegisterBlackoutDatesProperty = DependencyProperty.RegisterAttached("RegisterBlackoutDates", typeof(ObservableCollection<DateTime>), typeof(CalendarAttachedProperties), new PropertyMetadata(null, OnRegisterCommandBindingChanged));
public static void SetRegisterBlackoutDates(DependencyObject d, ObservableCollection<DateTime> value)
{
d.SetValue(RegisterBlackoutDatesProperty, value);
}
public static ObservableCollection<DateTime> GetRegisterBlackoutDates(DependencyObject d)
{
return (ObservableCollection<DateTime>)d.GetValue(RegisterBlackoutDatesProperty);
}
#endregion
#region Event Handlers
private static void CalendarBindings_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
ObservableCollection<DateTime> blackoutDates = sender as ObservableCollection<DateTime>;
Calendar calendar = _calendars.First(c => c.Tag == blackoutDates);
if (e.Action == NotifyCollectionChangedAction.Reset)
{
calendar.BlackoutDates.Clear();
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (DateTime date in e.NewItems)
{
calendar.BlackoutDates.Add(new CalendarDateRange(date));
}
}
}
private static void DatePickerBindings_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
ObservableCollection<DateTime> blackoutDates = sender as ObservableCollection<DateTime>;
DatePicker datePicker = _datePickers.First(c => c.Tag == blackoutDates);
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (DateTime date in e.NewItems)
{
datePicker.BlackoutDates.Add(new CalendarDateRange(date));
}
}
}
#endregion
#region Private Methods
private static void OnRegisterCommandBindingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Calendar calendar = sender as Calendar;
if (calendar != null)
{
ObservableCollection<DateTime> bindings = e.NewValue as ObservableCollection<DateTime>;
if (bindings != null)
{
if (!_calendars.Contains(calendar))
{
calendar.Tag = bindings;
_calendars.Add(calendar);
}
calendar.BlackoutDates.Clear();
foreach (DateTime date in bindings)
{
calendar.BlackoutDates.Add(new CalendarDateRange(date));
}
bindings.CollectionChanged += CalendarBindings_CollectionChanged;
}
}
else
{
DatePicker datePicker = sender as DatePicker;
if (datePicker != null)
{
ObservableCollection<DateTime> bindings = e.NewValue as ObservableCollection<DateTime>;
if (bindings != null)
{
if (!_datePickers.Contains(datePicker))
{
datePicker.Tag = bindings;
_datePickers.Add(datePicker);
}
datePicker.BlackoutDates.Clear();
foreach (DateTime date in bindings)
{
datePicker.BlackoutDates.Add(new CalendarDateRange(date));
}
bindings.CollectionChanged += DatePickerBindings_CollectionChanged;
}
}
}
}
#endregion
}
在我们的MainWindow.xaml里面引入命名空间xmlns:crackpot="clr-namespace:WPF_CMS.AttachedProperties"
关于整个窗口的设计 Title="客户管理系统" Height="600" Width="1000" Background="Transparent" AllowsTransparency="True" WindowStyle="None" WindowStartupLocation="CenterScreen" FontFamily="Cambria">
MainWindow.xaml
<Border Background="White" CornerRadius="30">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="240"/>
<ColumnDefinition Width="280"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!--header-->
<controls:HeaderControl Grid.ColumnSpan="3" Cursor=""/>
<StackPanel Grid.Row="1" Grid.Column="0">
<Button Content="添加客户" Click="ClearSelectedCustomer_Click" Width="195" Height="33" Margin="10"/>
<ListView ItemsSource="{Binding Customers, Mode=OneWay}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedCustomer, Mode=TwoWay}"/>
</StackPanel>
<MaterialDesign:Card Grid.Row="1" Grid.Column="1" Width="250" Height="440" Margin="10">
<StackPanel >
<Border Margin="10" CornerRadius="20" Background="#FFFFEEFA">
<Image Source="/Images/cartoon.png" Stretch="Uniform" Height="150"/>
</Border>
<TextBox x:Name="NameTextBox" Margin="10"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
MaterialDesign:HintAssist.Hint="姓名"
Text="{Binding SelectedCustomer.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Name="IdTextBox" Margin="10"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
MaterialDesign:HintAssist.Hint="身份证号"
Text="{Binding SelectedCustomer.IdNumber, Mode=TwoWay}"/>
<TextBox x:Name="AddressTextBox" Margin="10"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
MaterialDesign:HintAssist.Hint="家庭地址"
Text="{Binding SelectedCustomer.Address, Mode=TwoWay}"/>
<Button Content="保存" Margin="10 10 10 30" VerticalAlignment="Bottom" HorizontalAlignment="Left" Click="SaveCustomer_Click"/>
</StackPanel>
</MaterialDesign:Card>
<MaterialDesign:Card Grid.Row="1" Grid.Column="2" Width="270" Margin="35 30 35 30">
<StackPanel Grid.Row="1" Grid.Column="2">
<!--<ListView ItemsSource="{Binding Appointments, Mode=TwoWay}" DisplayMemberPath="Time"/>-->
<Calendar Name="AppointmentCalendar" Height="320" Width="300"
crackpot:CalendarAttachedProperties.RegisterBlackoutDates="{Binding Appointments, Mode=OneWay}"
SelectedDate="{Binding SelectedDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Cursor="Hand">
</Calendar>
<Button Content="预约" Click="AddAppointment_Click" Width="226" Cursor="Hand"/>
</StackPanel>
</MaterialDesign:Card>
</Grid>
</Border>
由于更改为日历点击预约,因此相关逻辑代码也需要更改调整
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private MainViewModel _viewModel;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainViewModel();
_viewModel.LoadCustomers();
DataContext = _viewModel;
}
private void ClearSelectedCustomer_Click(object sender, RoutedEventArgs e)
{
_viewModel.ClearSelectedCustomer();
}
private void SaveCustomer_Click(object sender, RoutedEventArgs e)
{
try
{
string name = NameTextBox.Text.Trim();
string idNumber = IdTextBox.Text.Trim();
string address = AddressTextBox.Text.Trim();
_viewModel.SaveCustomer(name, idNumber, address);
}catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void AddAppointment_Click(object sender, RoutedEventArgs e)
{
try
{
_viewModel.AddAppointment();
}catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
MainViewModel.cs
//INotifyPropertyChanged刷新更新的属性
public class MainViewModel : INotifyPropertyChanged
{
//初始化空列表避免程序运行过程中出现为止的内存问题
//public List<Customer> Customers { get; set; } = new();
//使用观察者模式ObservableCollection实时更新添加后的客户数据
public ObservableCollection<CustomerViewModel> Customers { get; set; } = new();
public ObservableCollection<DateTime> Appointments { get; set; } = new();
//selectedDate可能为空
private DateTime? _selectedDate;
public DateTime? SelectedDate
{
get => _selectedDate;
set
{
if(_selectedDate != value)
{
_selectedDate = value;
RaisePropertyChanged(nameof(SelectedDate));
}
}
}
private CustomerViewModel _selectedCustomer;
public event PropertyChangedEventHandler? PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public CustomerViewModel SelectedCustomer
{
get => _selectedCustomer;
set
{
if(value != _selectedCustomer)
{
_selectedCustomer = value;
RaisePropertyChanged(nameof(SelectedCustomer));
LoadAppointments(SelectedCustomer.Id);
}
}
}
public void LoadCustomers()
{
Customers.Clear();//重置客户列表
using (var db = new AppDbContext())
{
var customers = db.Customers.ToList();
foreach (var customer in customers)
{
Customers.Add(new CustomerViewModel(customer));
}
}
}
public void ClearSelectedCustomer()
{
_selectedCustomer = null;
RaisePropertyChanged(nameof(SelectedCustomer));
}
public void SaveCustomer(string name, string idNumber, string address)
{
if(SelectedCustomer != null)
{
//更新客户数据
using (var db = new AppDbContext())
{
var customer = db.Customers.Where(c => c.Id == SelectedCustomer.Id).FirstOrDefault();
customer.Name = name;
customer.IdNumber = idNumber;
customer.Address = address;
db.SaveChanges();
}
}
else
{
//添加新客户
using (var db = new AppDbContext())
{
var newCustomer = new Customer()
{
Name = name,
IdNumber = idNumber,
Address = address
};
db.Customers.Add(newCustomer);
db.SaveChanges();
}
LoadCustomers();
}
}
public void LoadAppointments(int customerId)
{
Appointments.Clear();
using (var db = new AppDbContext())
{
var appointments = db.Appointments.Where(a => a.CustomerId == customerId).ToList();
foreach(var a in appointments)
{
Appointments.Add(a.Time);
}
}
}
public void AddAppointment()
{
if(SelectedCustomer == null) { return; }
using (var db = new AppDbContext())
{
var newAppointment = new Appointment()
{
Time = SelectedDate.Value,
CustomerId = SelectedCustomer.Id
};
db.Appointments.Add(newAppointment);
db.SaveChanges();
}
SelectedDate = null;
LoadAppointments(SelectedCustomer.Id);
}
}
到此,本项目结束