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

一、Entity Framework Core

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

EF的组件
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示意图
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
接着访问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);
    }
}

到此,本项目结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/347427.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity通用渲染管线升级URP、HDRP

Unity通用渲染管线升级URP、HDRP 一、Build-in Pipline升级到 URP 一、Build-in Pipline升级到 URP 安装URP包 升级所有材质&#xff08;升级完成后材质会变成紫红色&#xff0c;Shader丢失&#xff0c;此为正常现象&#xff09; 创建 UniversalRenderPipelineAsset 配置文…

java web 校园健康管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java Web校园健康管理系统是一套完善的java web信息管理系统 &#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysq…

深入理解工厂模式:创建可复用的对象实例

这里写目录标题 前言简单工厂模式工厂方法模式抽象工厂模式总结 前言 工厂模式是一种常用的设计模式&#xff0c;它可以帮助我们更好地组织和管理代码&#xff0c;将对象的创建和使用分离开来&#xff0c;提高代码的可维护性和扩展性。 在软件开发中&#xff0c;我们经常会遇到…

C++拷贝构造函数、赋值学习整理:

拷贝构造函数&#xff1a; 概念&#xff1a; 构造函数的第一个参数&#xff0c;是类本身的const引用&#xff08;一般情况下没有其他参数&#xff0c;少数情况&#xff1a;其他参数必须有默认值&#xff01;&#xff09;称此类构造函数为拷贝构造函数 特征&#xff1a; 1&am…

使用Animate.css动画库

1.网站&#xff1a;Animate.css | A cross-browser library of CSS animations. 样式&#xff1a;Animate.css 一款强大的预设css3动画库 (jq22.com) 一、引入 命令提示符/终端&#xff1a; npm install animate.css --save 二、 全局导入&#xff08;在main.js&#xff0…

Obsidian笔记软件结合cpolar实现安卓移动端远程本地群晖WebDAV数据同步

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

如何编写高质量测试用例?

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;薪资嘎嘎涨 测试场景&#xff1a; 为登录功能设计测试用例 测试员为什么要会编测试用例 测试员的目标是…

HAL STM32+EC11编码器实现增减调节及单击、双击、长按功能

HAL STM32EC11编码器实现增减调节及单击、双击、长按功能 &#x1f4fa;实现效果演示&#xff1a; &#x1f4d8;内容提要 &#x1f4dd;本文主要实现&#xff0c;通过STM32 HAL库开发&#xff0c;实现的EC11编码器功能&#xff0c;按键结合状态机思想实现的拓展单击、双击、…

win下安装es可视化工具——elasticsearch head(win_Elasticsearch)

一、head简介 Elasticsearch Head是集群管理、数据可视化、增删改查、查询语句可视化工具。 二、node.js的安装 ElasticSearch-head 依赖于node.js 下面先安装node.js 下面是node.js下载地址http://nodejs.cn/download/&#xff1b; 下载后&#xff0c;就是一个安装包&#xf…

如何在Ubuntu安装配置SVN服务端并实现无公网ip访问内网资料库

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改au…

Java可以用于物联网的开发吗?

Java可以用于物联网的开发吗? 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「Java的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;J…

备忘录记事本内容转移到新手机的方法

在日常的工作和生活中&#xff0c;我习惯用备忘录来记录一切&#xff1a;工作的要点、买菜的清单、生活的琐事……这些看似微小的记录&#xff0c;却是我生活的重要组成部分。然而&#xff0c;每次换手机&#xff0c;我总是面临一个难题&#xff1a;如何将旧手机上的备忘录内容…

下沉市场哪些品牌正当红?“下沉同花顺”异军突起

文 | 螳螂观察 作者 | 易不二 2023年的消费市场&#xff0c;越来越多“农村包围城市”的下沉品牌&#xff0c;以亮眼的表现成为拉动消费复苏的主力军。 全球36000多家门店的蜜雪冰城&#xff0c;向港交所递表冲刺IPO&#xff1b;两大量贩零食巨头赵一鸣零食与零食很忙战略合…

一个响指,代码生成!华为云CodeArts Snap正式公测

月初&#xff0c;华为云CodeArts Snap正式开启公测&#xff0c;这是一款基于华为云研发大模型的智能化编程助手&#xff0c;旨在为开发者提供高效且智能的编程体验&#xff0c;提升研发人员的单兵作战能力。 如今&#xff0c;生成式AI爆发式增长&#xff0c;大模型商用节奏加快…

JVM/GC复习

JVM/GC JVM(java虚拟机)MATjstack(将正在运行的JVM的线程进行快照并且打印出来)死锁VisualVM工具(监控线程内存使用情况)JMX GC垃圾回收算法1.引用计数法2.标记清除发3.标记压缩算法4.复制算法5.分代算法 收集器1.串行垃圾收集器2.并行垃圾收集器2.CMS垃圾收集器 3.G1垃圾收集器…

威联通QNAP NAS结合cpolar内网穿透实现公网远程访问NAS中存储的文件

文章目录 推荐 前言1. 威联通安装cpolar内网穿透2. 内网穿透2.1 创建隧道2.2 测试公网远程访问 3. 配置固定二级子域名3.1 保留二级子域名3.2 配置二级子域名 4. 使用固定二级子域名远程访问 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣…

一文了解prettier

简介 eslint和prettier都是用来做代码格式化的&#xff0c;他们其中又分为npm包和vscode插件&#xff0c;顺序是&#xff1a;先有npm包再有vscode插件 eslint限制工具 作用&#xff1a; 检查代码规范变量声明是否调用是否有console.log()语句… 用法 新建index.js文件 //…

MySQL数据库的锁机制

目录 一、引言 二、锁的类型及作用 2.1 行级锁 2.2 间隙锁与临键锁 2.3 共享锁与排他锁 2.4 意向锁 2.5 表级锁 2.6 元数据锁 三、锁的管理与优化 3.1 合理设置事务隔离级别 3.2 避免长事务 3.3 索引优化 3.4 明确锁定范围 3.5 避免不必要的全表扫描 四、实战分…

JDBC数据库连接

JDBC(Java DataBase Connectivity)就是用Java语言操作关系型数据库的一套API JDBC的步骤固定&#xff0c;大体分为8个步骤&#xff0c;以MySQL数据库为例 1.创建工程并导入驱动jar包 2.注册驱动 注册驱动的目的是告诉代码要执行哪一个jar包 Class.forName(com.mysql.jdbc.D…

多尺度特征融合13种创新方案全面汇总,含2024年最新

前段时间和大佬朋友交流学术的时候&#xff0c;发现目前发论文最好用的2大创新方式一是加注意力机制&#xff0c;二是多尺度特征融合。上回我们讲过了加注意力机制&#xff0c;今天我们就来聊聊多尺度特征融合。 多尺度特征融合是一种在图像处理和CV中使用的技术&#xff0c;由…