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

news2024/11/15 23:45:58

一、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);
    }
}

到此,本项目结束

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【GitHub项目推荐--生成你的马赛克风格头像】【转载】

这个开源项目有意思&#xff0c;这是一个利用各种其他更小的图像来创建新图像的工具&#xff0c;小的图像包括圆圈、线条、波浪、十字绣、积木、Minecraft 积木、回形针、字母等&#xff0c;所以通过这些小图像开生成的新图像的可能性是无限的。 所以&#xff0c;它与其他马赛…

微信小程序(十四)分包和分包预加载

注释很详细&#xff0c;直接上代码 新增内容&#xff1a; 1.分包的配置 2.分包预加载的写法 先说说为什么需要分包&#xff1a; 小程序追求小而快&#xff0c;主包的大小控制是小程序上线的硬性要求&#xff0c;分包有利于小程序优化加载速度 分包的注意事项&#xff1a; 单个分…

Sulfo-Cy3-dCTP,Sulfo Cyanine3 dCTP,能够发出高强度的荧光信号

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;Sulfo-Cy3-dCTP&#xff0c;Sulfo Cyanine3 dCTP&#xff0c;磺化-Cy3-dCTP&#xff0c;Sulfo-Cyanine3-dCTP 一、基本信息 产品简介&#xff1a;Sulfo Cy3 dCTP, a water-soluble cyanine dye CY3 labeled dCTP, i…

链表OJ---排序链表

https://leetcode.cn/problems/7WHec2/description/ //合并 struct ListNode* merge_link(struct ListNode* head1, struct ListNode* head2) {struct ListNode* temhead malloc(sizeof(struct ListNode));temhead->val 0;struct ListNode *tmp temhead, *cur1 head1, *…

Endnote中文献数据库的整体导出迁移,以及导入到新计算机具体步骤-以Endnote X8为例

Endnote中文献数据库的整体导出迁移&#xff0c;以及导入到新计算机具体步骤-以Endnote X8为例 在学习和研究中&#xff0c;有时会使用文献管理工具Endnote会积累一些重要的文献&#xff0c;当对某个问题进行了深入研究后&#xff0c;可能会积累成百上千的文献及其文献批注等。…

【GitHub项目推荐--网站归档平台】【转载】

ArchiveBox&#xff1a;网站归档平台 ArchiveBox 是基于 Python 的强大网页归档解决方案&#xff0c;可以自动把网页变成静态页面&#xff08;HTML、PDF、图片&#xff09;&#xff0c;并自动提取和保存文本、音频等内容&#xff0c;可以用来做镜像站、档案馆、离线阅读等。 开…

笔记本外接显示器的配置方法

目录 第一步 点击左下角&#xff0c;选择“设置” 第二步 选择系统&#xff0c;默认进入屏幕页面&#xff0c;页面往下拉&#xff0c;找到多显示器下拉框 前提&#xff1a;插好显示器的电源插头且插上笔记本HDMI线 第一步 点击左下角&#xff0c;选择“设置” 第二步 选择系…

探索 XMLHttpRequest:网页与服务器的异步通信之道(上)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

gin路由篇

1. 基本路由 gin 框架中采用的路由库是基于httprouter做的 import ("net/http""github.com/gin-gonic/gin" )func main() {// 1.创建路由r : gin.Default()// 2.绑定路由规则&#xff0c;执行的函数// gin.Context&#xff0c;封装了request和responser.…

C++内存泄漏检测工具

在程序中增加相应的内存检测工具 #define CRTDBG MAP ALLOC #include <stdlib.h> #include <crtdbg.h> #ifdef DEBUG #ifndef DBGNEW #define DBG_NEW new (_NORMAL_BLOCK&#xff0c;_FILE_LINE_) #define new DBG NEW #endif #endif _CrtDumpMemoryLeaks(); …

linux条件判断练习

1.实现自动生成相应的压缩包 1.写一个脚本&#xff0c;完成如下功能 传递一个参数给脚本&#xff0c;此参数为gzip、bzip2或者xz三者之一&#xff1b; (1) 如果参数1的值为gzip&#xff0c;则使用tar和gzip归档压缩/etc目录至/backups目录中&#xff0c;并命名为/backups/etc-…

2024年图像处理与大数据信息应用国际会议(ICIPCDIA 2024)

2024年图像处理与大数据信息应用国际会议(ICIPCDIA 2024) 2024 International Conference on Image Processing and Big Data Information Applications(ICIPCDIA 2024) 数据库&#xff1a;EI,CPCI,CNKI,Google Scholar等检索 一、【会议简介】 ​2024年图像处理与大数据信息应…

李宏毅《机器学习 深度学习》简要笔记(一)

一、线性回归中的模型选择 上图所示&#xff1a; 五个模型&#xff0c;一个比一个复杂&#xff0c;其中所包含的function就越多&#xff0c;这样就有更大几率找到一个合适的参数集来更好的拟合训练集。所以&#xff0c;随着模型的复杂度提高&#xff0c;train error呈下降趋势…

专业130+总分420+上海交通大学819考研经验分享上海交大电子信息与通信工程

今年专业课819信号系统与信息处理&#xff08;ss和dsp&#xff09;130&#xff0c;总分420&#xff0c;如愿梦圆交大&#xff0c;以下总结了自己这一年专业课&#xff0c;基础课复习经历&#xff0c;希望对大家复习有所帮助。专业课819信号系统与信号处理&#xff1a; 交大819…

Redisson 分布式锁解决主从一致性问题的原理

目录 一、主从不一致产生原因 二、Redisson 解决主从一致性的原理 一、主从不一致产生原因 1. Redis 主从集群&#xff1a;主从读写分离&#xff0c;主节点将数据同步给从节点 主节点&#xff1a;增删改从节点&#xff1a;读 2. 主从同步存在延迟&#xff0c;若主节点宕机…

基于Python Django的大数据招聘数据分析系统,包括数据大屏和后台管理

基于Python Django的大数据招聘数据分析系统是一个综合利用大数据技术和数据可视化技术的招聘行业解决方案。该系统旨在帮助企业和招聘机构更好地理解和分析招聘市场的趋势和变化&#xff0c;从而提高招聘效率和质量。 首先&#xff0c;该系统提供了一个强大的后台管理功能&am…

数据结构之受限线性表

受限线性表 对于一般线性表&#xff0c;虽然必须通过遍历逐一查找再对目标位置进行增、删和查操作&#xff0c;但至少一般线性表对于可操作元素并没有限制。说到这里&#xff0c;大家应该明白了&#xff0c;所谓的受限线性表&#xff0c;就是可操作元素受到了限制。 受限线性表…

【数据库连接】连接sqlite3报错:go-sqlite3 requires cgo to work. This is a stub

报错信息 register db Ping default, Binary was compiled with ‘CGO_ENABLED0’, go-sqlite3 requires cgo to work. This is a stubWindows解决办法 新建环境变量 新报错 Failed to build the application: # runtime/cgo cgo: C compiler “gcc” not found: exec: “gc…

如何为视频内多角色进行翻译配音?含教程和案例

多角色翻译配音有哪些实用场景&#xff1f; 多角色翻译配音可以应用于任何需要在音频或视频中区分不同角色的情境中&#xff0c;以提高听众的理解和体验。这些场景都需要专业的配音演员和翻译人员来确保高质量的表现。多角色翻译配音在不同情境下都可以有实用场景&#xff0c;…

堆的概念,性质及其实现

1.堆的概念及结构 如果有一个关键码的集合K { &#xff0c; &#xff0c; &#xff0c;…&#xff0c; }&#xff0c;把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中&#xff0c;并满足&#xff1a; < 且 < ( > 且 > ) i 0&#xff0c;1&#x…