C#学习(十)——WPF重构与美化

一、Entity Framework Core

特点:【跨平台】,【建模】,【查询、更改、保存】,【并发】,【事务】,【缓存】,【数据迁移】

EF的组件

二、重构:构建数据模型

项目延续C#学习(九)的 项目代码,以此基础进行进一步重构

所需的NuGet包如下:

逆向数据库获得数据模型(Model)

首先在根目录下创建Models文件夹,然后使用Tools->NuGet包管理器->程序包管理器控制台

输入指令

bash 复制代码
Scaffold-DbContext "自己的数据库连接字符串" Microsoft.EntityFrameworkCore.Sqlserver -OutputDir Models -Context AppDbContext

处理完成后,就可以在Models文件夹中看到通过逆向数据库构建的数据模型啦!

三、OMR数据管理

使用Entity Framework 取代SQL语句

使用ORM来自动生成SQL语句,通过数据库的映射框架获取数据模型,通过模型框架的链式结构来处理数据,可以使业务实现在代码中,而不是实现在SQL语句中。因此对于程序员来说,使用对象的链式结构更加符合面向对象的编程理念。

通过数据模型向UI传递和绑定数据

代码改进后如下:

MainWindow.xaml.cs

csharp 复制代码
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)

四、布局重构

首先对于原来丑陋的展示页面进行重新布局,构建我们的基础布局框架

xml 复制代码
 <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

xml 复制代码
<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中对应部分删除,替换为

xml 复制代码
 <!--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

xml 复制代码
<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

csharp 复制代码
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

csharp 复制代码
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

csharp 复制代码
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

csharp 复制代码
//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文件中,即可应用

示例代码:

xml 复制代码
<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

csharp 复制代码
// 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

xml 复制代码
<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

csharp 复制代码
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

csharp 复制代码
//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);
    }
}

到此,本项目结束

相关推荐
李小星同志4 分钟前
高级算法设计与分析 学习笔记6 B树
笔记·学习
霜晨月c15 分钟前
MFC 使用细节
笔记·学习·mfc
小江湖199429 分钟前
元数据保护者,Caesium压缩不丢重要信息
运维·学习·软件需求·改行学it
friklogff43 分钟前
【无标题】云端之C#:全面解析6种云服务提供商的SDK
开发语言·flask·c#
dot.Net安全矩阵1 小时前
.NET内网实战:通过命令行解密Web.config
前端·学习·安全·web安全·矩阵·.net
微刻时光1 小时前
Redis集群知识及实战
数据库·redis·笔记·学习·程序人生·缓存
c#上位机2 小时前
C#事件的用法
java·javascript·c#
chnyi6_ya2 小时前
一些写leetcode的笔记
笔记·leetcode·c#
IT规划师2 小时前
C#|.net core 基础 - 扩展数组添加删除性能最好的方法
c#·.netcore·数组
时光追逐者3 小时前
分享6个.NET开源的AI和LLM相关项目框架
人工智能·microsoft·ai·c#·.net·.netcore