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

这篇具有很好参考价值的文章主要介绍了C#学习(十)——WPF重构与美化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、Entity Framework Core

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

EF的组件
C#学习(十)——WPF重构与美化,c#,学习,wpf,重构,ui

二、重构:构建数据模型

项目延续C#学习(九)的 项目代码,以此基础进行进一步重构
所需的NuGet包如下:
C#学习(十)——WPF重构与美化,c#,学习,wpf,重构,ui
C#学习(十)——WPF重构与美化,c#,学习,wpf,重构,ui

逆向数据库获得数据模型(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的交互行为。
C#学习(十)——WPF重构与美化,c#,学习,wpf,重构,ui
MVVM的优点

  • [ 兼容MVC架构 ]
  • [ 业务与UI逻辑彻底分开,方便测试 ]
  • [ 方便维护 ]

MVVM的缺点

  • [ 代码量增加 ]
  • [ 对象调用复杂度增加 ]

MVVM项目代码重构
首先,在根目录下创建文件夹ViewModels,新建项目MainViewModel,CustomerViewModel,AppointmentViewModel
代码如下:
MainWindow.xaml

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="240"/>
        <ColumnDefinition Width="280"/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    
    <!--header-->
    <controls:HeaderControl Grid.ColumnSpan="3"/>
    <StackPanel Grid.Row="1" Grid.Column="0">
        <Button Content="添加客户" Click="ClearSelectedCustomer_Click"/>
        <ListView ItemsSource="{Binding Customers, Mode=OneWay}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedCustomer, Mode=TwoWay}"/>
    </StackPanel>
    <StackPanel Grid.Row="1" Grid.Column="1">
        <TextBlock Text="姓名" Margin="10 10 10 0"/>
        <TextBox x:Name="NameTextBox" Margin="10" Text="{Binding SelectedCustomer.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
        <TextBlock Text="身份证号" Margin="10 10 10 0"/>
        <TextBox Name="IdTextBox"  Margin="10" Text="{Binding SelectedCustomer.IdNumber, Mode=TwoWay}"/>
        <TextBlock Text="地址" Margin="10 10 10 0"/>
        <TextBox x:Name="AddressTextBox" Margin="10" Text="{Binding SelectedCustomer.Address, Mode=TwoWay}"/>
        <Button Content="保存" Margin="10 10 10 30" VerticalAlignment="Bottom" HorizontalAlignment="Left" Click="SaveCustomer_Click"/>
    </StackPanel>
    <StackPanel Grid.Row="1" Grid.Column="2">
        <ListView ItemsSource="{Binding Appointments, Mode=TwoWay}" DisplayMemberPath="Time"/>
        <TextBlock Text="添加新预约"/>
        <DatePicker Name="AppointmentDatePicker" Margin="10"/>
        <Button Content="预约" Click="AddAppointment_Click"/>
    </StackPanel> 
</Grid>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
    private MainViewModel _viewModel;
    public MainWindow()
    {
        InitializeComponent();
        _viewModel = new MainViewModel();

        _viewModel.LoadCustomers();

        DataContext = _viewModel;
    }

    private void ClearSelectedCustomer_Click(object sender, RoutedEventArgs e)
    {
        _viewModel.ClearSelectedCustomer();
    }

    private void SaveCustomer_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            string name = NameTextBox.Text.Trim();
            string idNumber = IdTextBox.Text.Trim();
            string address = AddressTextBox.Text.Trim();

            _viewModel.SaveCustomer(name, idNumber, address);
        }catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }

    private void AddAppointment_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            DateTime time = DateTime.Parse(AppointmentDatePicker.Text);
            _viewModel.AddAppointment(time);
        }catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }
}

AppointmentViewModel.cs

public class AppointmentViewModel
{
    private Appointment _appointment;

    public AppointmentViewModel(Appointment appointment)
    {
        _appointment = appointment;
    }
	//因为Id为只读属性,因此不需要set
    public int Id { get => _appointment.Id; }

    public DateTime Time { get => _appointment.Time; set
        {
        	//有且仅当数据发生变化,才向数据库写入数据
            if(value != _appointment.Time)
            {
                _appointment.Time = value;
            }
        }      
    }
}

CustomerViewModel.cs

public class CustomerViewModel
{
    private Customer _customer;

    public CustomerViewModel(Customer customer)
    {
        _customer = customer;
    }

    public int Id { get => _customer.Id; }

    public string Name { get => _customer.Name; set 
        {
            if(_customer.Name != value)
            {
                _customer.Name = value;
            }
        } 
    }public string IdNumber { get => _customer.IdNumber; set 
        {
            if(_customer.IdNumber != value)
            {
                _customer.IdNumber = value;
            }
        } 
    }public string Address { get => _customer.Address; set 
        {
            if(_customer.Address != value)
            {
                _customer.Address = value;
            }
        } 
    }
}

MainViewModel.cs

//INotifyPropertyChanged刷新更新的属性
public class MainViewModel : INotifyPropertyChanged
{
    //初始化空列表避免程序运行过程中出现为止的内存问题
    //使用观察者模式ObservableCollection实时更新添加后的客户数据
    public ObservableCollection<CustomerViewModel> Customers { get; set; } = new();
    public ObservableCollection<AppointmentViewModel> Appointments { get; set; } = new();

    private CustomerViewModel _selectedCustomer;

    public event PropertyChangedEventHandler? PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public CustomerViewModel SelectedCustomer
    {
        get => _selectedCustomer;
        set 
        { 
            if(value != _selectedCustomer)
            {
                _selectedCustomer = value;
                RaisePropertyChanged(nameof(SelectedCustomer));
                LoadAppointments(SelectedCustomer.Id);
            }
        } 
    }
    public void LoadCustomers() 
    {
        Customers.Clear();//重置客户列表
        using (var db = new AppDbContext())
        {
            var customers = db.Customers.ToList();

            foreach (var customer in customers)
            {
                Customers.Add(new CustomerViewModel(customer));
            }

        }
    }

    public void ClearSelectedCustomer()
    {
        _selectedCustomer = null;
        RaisePropertyChanged(nameof(SelectedCustomer));
    }

    public void SaveCustomer(string name, string idNumber, string address)
    {
        if(SelectedCustomer != null)
        {
            //更新客户数据
            using (var db = new AppDbContext())
            {
                var customer = db.Customers.Where(c => c.Id == SelectedCustomer.Id).FirstOrDefault();
                customer.Name = name;
                customer.IdNumber = idNumber;
                customer.Address = address;
                db.SaveChanges();
            }
        }
        else
        {
            //添加新客户
            using (var db = new AppDbContext())
            {
                var newCustomer = new Customer()
                {
                    Name = name,
                    IdNumber = idNumber,
                    Address = address
                };
                db.Customers.Add(newCustomer);
                db.SaveChanges();
            }
            LoadCustomers();
        }
    }

    public void LoadAppointments(int customerId)
    {
        Appointments.Clear();
        using (var db = new AppDbContext())
        {
            var appointments = db.Appointments.Where(a => a.CustomerId == customerId).ToList();
            foreach(var a in appointments)
            {
                Appointments.Add(new AppointmentViewModel(a));
            }
        }
    }

    public void AddAppointment(DateTime selectedDate)
    {
        if(SelectedCustomer == null) { return; }

        using (var db = new AppDbContext())
        {
            var newAppointment = new Appointment()
            {
                Time = selectedDate,
                CustomerId = SelectedCustomer.Id
            };
            db.Appointments.Add(newAppointment);
            db.SaveChanges();
        }
        LoadAppointments(SelectedCustomer.Id);
    }
}

六、Material UI框架

安装Material UI框架
C#学习(十)——WPF重构与美化,c#,学习,wpf,重构,ui
接着访问Material Design Themes的项目URL,可以看到对于此框架的使用讲解,将示例中的想使用颜色模式的代码部分,复制粘贴到App.xaml文件中,即可应用
示例代码:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <materialDesign:BundledTheme BaseTheme="Light" PrimaryColor="DeepPurple" SecondaryColor="Lime" />
            <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

在进一步优化中,我们想要实现在日历上显示预约,对于已经有预约的日期,不可在预约这样的效果。想到可以使用BlackoutDates,然而BlackoutDates不支持数据的绑定,也就是无法传入数据,因此需要使用其他方法进行。
这里借用作大神的方法进行操作,完美解决我们的需求!
根目录创建文件夹AttachedProperties,创建文件CalendarAttachedProperties
在大神基础上对于我们的项目略加调整------原答案地址
CalendarAttachedProperties.cs

// Adds a collection of command bindings to a date picker's existing BlackoutDates collection, since the collections are immutable and can't be bound to otherwise.
// Usage: <DatePicker hacks:AttachedProperties.RegisterBlackoutDates="{Binding BlackoutDates}" >
public class CalendarAttachedProperties : DependencyObject
{
    #region Attributes

    private static readonly List<Calendar> _calendars = new List<Calendar>();
    private static readonly List<DatePicker> _datePickers = new List<DatePicker>();

    #endregion

    #region Dependency Properties

    public static DependencyProperty RegisterBlackoutDatesProperty = DependencyProperty.RegisterAttached("RegisterBlackoutDates", typeof(ObservableCollection<DateTime>), typeof(CalendarAttachedProperties), new PropertyMetadata(null, OnRegisterCommandBindingChanged));

    public static void SetRegisterBlackoutDates(DependencyObject d, ObservableCollection<DateTime> value)
    {
        d.SetValue(RegisterBlackoutDatesProperty, value);
    }

    public static ObservableCollection<DateTime> GetRegisterBlackoutDates(DependencyObject d)
    {
        return (ObservableCollection<DateTime>)d.GetValue(RegisterBlackoutDatesProperty);
    }

    #endregion

    #region Event Handlers

    private static void CalendarBindings_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        ObservableCollection<DateTime> blackoutDates = sender as ObservableCollection<DateTime>;

        Calendar calendar = _calendars.First(c => c.Tag == blackoutDates);

        if (e.Action == NotifyCollectionChangedAction.Reset)
        {
            calendar.BlackoutDates.Clear();
        }

        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            foreach (DateTime date in e.NewItems)
            {
                calendar.BlackoutDates.Add(new CalendarDateRange(date));
            }
        }
    }

    private static void DatePickerBindings_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        ObservableCollection<DateTime> blackoutDates = sender as ObservableCollection<DateTime>;

        DatePicker datePicker = _datePickers.First(c => c.Tag == blackoutDates);

        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            foreach (DateTime date in e.NewItems)
            {
                datePicker.BlackoutDates.Add(new CalendarDateRange(date));
            }
        }
    }

    #endregion

    #region Private Methods

    private static void OnRegisterCommandBindingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        Calendar calendar = sender as Calendar;
        if (calendar != null)
        {
            ObservableCollection<DateTime> bindings = e.NewValue as ObservableCollection<DateTime>;
            if (bindings != null)
            {
                if (!_calendars.Contains(calendar))
                {
                    calendar.Tag = bindings;
                    _calendars.Add(calendar);
                }

                calendar.BlackoutDates.Clear();
                foreach (DateTime date in bindings)
                {
                    calendar.BlackoutDates.Add(new CalendarDateRange(date));
                }
                bindings.CollectionChanged += CalendarBindings_CollectionChanged;
            }
        }
        else
        {
            DatePicker datePicker = sender as DatePicker;
            if (datePicker != null)
            {
                ObservableCollection<DateTime> bindings = e.NewValue as ObservableCollection<DateTime>;
                if (bindings != null)
                {
                    if (!_datePickers.Contains(datePicker))
                    {
                        datePicker.Tag = bindings;
                        _datePickers.Add(datePicker);
                    }

                    datePicker.BlackoutDates.Clear();
                    foreach (DateTime date in bindings)
                    {
                        datePicker.BlackoutDates.Add(new CalendarDateRange(date));
                    }
                    bindings.CollectionChanged += DatePickerBindings_CollectionChanged;
                }
            }
        }
    }

    #endregion
}

在我们的MainWindow.xaml里面引入命名空间xmlns:crackpot="clr-namespace:WPF_CMS.AttachedProperties"
关于整个窗口的设计 Title="客户管理系统" Height="600" Width="1000" Background="Transparent" AllowsTransparency="True" WindowStyle="None" WindowStartupLocation="CenterScreen" FontFamily="Cambria">
MainWindow.xaml

<Border Background="White" CornerRadius="30">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="240"/>
            <ColumnDefinition Width="280"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <!--header-->
        <controls:HeaderControl Grid.ColumnSpan="3" Cursor=""/>
        <StackPanel Grid.Row="1" Grid.Column="0">
            <Button Content="添加客户" Click="ClearSelectedCustomer_Click" Width="195" Height="33" Margin="10"/>
            <ListView ItemsSource="{Binding Customers, Mode=OneWay}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedCustomer, Mode=TwoWay}"/>
        </StackPanel>
        <MaterialDesign:Card Grid.Row="1" Grid.Column="1" Width="250" Height="440" Margin="10">
            <StackPanel >
                <Border Margin="10" CornerRadius="20" Background="#FFFFEEFA">
                    <Image Source="/Images/cartoon.png" Stretch="Uniform" Height="150"/>
                </Border>
                <TextBox x:Name="NameTextBox" Margin="10" 
                         Style="{StaticResource MaterialDesignOutlinedTextBox}"
                         MaterialDesign:HintAssist.Hint="姓名"
                         Text="{Binding SelectedCustomer.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                <TextBox Name="IdTextBox"  Margin="10" 
                         Style="{StaticResource MaterialDesignOutlinedTextBox}"
                         MaterialDesign:HintAssist.Hint="身份证号"
                         Text="{Binding SelectedCustomer.IdNumber, Mode=TwoWay}"/>
                <TextBox x:Name="AddressTextBox" Margin="10" 
                         Style="{StaticResource MaterialDesignOutlinedTextBox}"
                         MaterialDesign:HintAssist.Hint="家庭地址"
                         Text="{Binding SelectedCustomer.Address, Mode=TwoWay}"/>
                <Button Content="保存" Margin="10 10 10 30" VerticalAlignment="Bottom" HorizontalAlignment="Left" Click="SaveCustomer_Click"/>
            </StackPanel>
        </MaterialDesign:Card>
        <MaterialDesign:Card Grid.Row="1" Grid.Column="2" Width="270" Margin="35 30 35 30">
            <StackPanel Grid.Row="1" Grid.Column="2">
                <!--<ListView ItemsSource="{Binding Appointments, Mode=TwoWay}" DisplayMemberPath="Time"/>-->
                <Calendar Name="AppointmentCalendar" Height="320" Width="300" 
                          crackpot:CalendarAttachedProperties.RegisterBlackoutDates="{Binding Appointments, Mode=OneWay}"
                          SelectedDate="{Binding SelectedDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Cursor="Hand">
                </Calendar>
                <Button Content="预约" Click="AddAppointment_Click" Width="226" Cursor="Hand"/>
            </StackPanel>
        </MaterialDesign:Card>
    </Grid>
</Border>

由于更改为日历点击预约,因此相关逻辑代码也需要更改调整
MainWindow.xaml.cs

public partial class MainWindow : Window
{
    private MainViewModel _viewModel;
    public MainWindow()
    {
        InitializeComponent();
        _viewModel = new MainViewModel();

        _viewModel.LoadCustomers();

        DataContext = _viewModel;

    }

    private void ClearSelectedCustomer_Click(object sender, RoutedEventArgs e)
    {
        _viewModel.ClearSelectedCustomer();
    }

    private void SaveCustomer_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            string name = NameTextBox.Text.Trim();
            string idNumber = IdTextBox.Text.Trim();
            string address = AddressTextBox.Text.Trim();

            _viewModel.SaveCustomer(name, idNumber, address);
        }catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }

    private void AddAppointment_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            _viewModel.AddAppointment();
        }catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }
}

MainViewModel.cs

//INotifyPropertyChanged刷新更新的属性
public class MainViewModel : INotifyPropertyChanged
{
    //初始化空列表避免程序运行过程中出现为止的内存问题
    //public List<Customer> Customers { get; set; } = new();
    //使用观察者模式ObservableCollection实时更新添加后的客户数据
    public ObservableCollection<CustomerViewModel> Customers { get; set; } = new();
    public ObservableCollection<DateTime> Appointments { get; set; } = new();
	//selectedDate可能为空
    private DateTime? _selectedDate;
    public DateTime? SelectedDate
    {
        get => _selectedDate;
        set
        {
            if(_selectedDate != value)
            {
                _selectedDate = value;
                RaisePropertyChanged(nameof(SelectedDate));
            }
        }
    }

    private CustomerViewModel _selectedCustomer;

    public event PropertyChangedEventHandler? PropertyChanged;

    private void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public CustomerViewModel SelectedCustomer
    {
        get => _selectedCustomer;
        set 
        { 
            if(value != _selectedCustomer)
            {
                _selectedCustomer = value;
                RaisePropertyChanged(nameof(SelectedCustomer));
                LoadAppointments(SelectedCustomer.Id);
            }
        } 
    }
    public void LoadCustomers() 
    {
        Customers.Clear();//重置客户列表
        using (var db = new AppDbContext())
        {
            var customers = db.Customers.ToList();

            foreach (var customer in customers)
            {
                Customers.Add(new CustomerViewModel(customer));
            }

        }
    }

    public void ClearSelectedCustomer()
    {
        _selectedCustomer = null;
        RaisePropertyChanged(nameof(SelectedCustomer));
    }

    public void SaveCustomer(string name, string idNumber, string address)
    {
        if(SelectedCustomer != null)
        {
            //更新客户数据
            using (var db = new AppDbContext())
            {
                var customer = db.Customers.Where(c => c.Id == SelectedCustomer.Id).FirstOrDefault();
                customer.Name = name;
                customer.IdNumber = idNumber;
                customer.Address = address;
                db.SaveChanges();
            }
        }
        else
        {
            //添加新客户
            using (var db = new AppDbContext())
            {
                var newCustomer = new Customer()
                {
                    Name = name,
                    IdNumber = idNumber,
                    Address = address
                };
                db.Customers.Add(newCustomer);
                db.SaveChanges();
            }
            LoadCustomers();
        }
    }

    public void LoadAppointments(int customerId)
    {
        Appointments.Clear();
        using (var db = new AppDbContext())
        {
            var appointments = db.Appointments.Where(a => a.CustomerId == customerId).ToList();
            foreach(var a in appointments)
            {
                Appointments.Add(a.Time);
            }
        }
    }

    public void AddAppointment()
    {
        if(SelectedCustomer == null) { return; }

        using (var db = new AppDbContext())
        {
            var newAppointment = new Appointment()
            {
                Time = SelectedDate.Value,
                CustomerId = SelectedCustomer.Id
            };
            db.Appointments.Add(newAppointment);
            db.SaveChanges();
        }
        SelectedDate = null;
        LoadAppointments(SelectedCustomer.Id);
    }
}

到此,本项目结束文章来源地址https://www.toymoban.com/news/detail-823775.html

到了这里,关于C#学习(十)——WPF重构与美化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C# 计时器(Timer )WPF窗体出现“System.InvalidOperationException:“调用线程必须为 STA,因为许多 UI 组件都需要。””

    大家在WPF窗体使用计时器(Timer)的时候可能会出现一个这样的错误“System.InvalidOperationException:“调用线程必须为 STA,因为许多 UI 组件都需要。””,这个错误一般都是线程的问题,我们可以使用另一种计时器(DispatcherTimer)来避免发生这种问题。 以上就是解决Timer计时器出

    2024年02月04日
    浏览(53)
  • C# WPF布局

    布局: 1、Grid: Window x:Class=\\\"WpfApp2.MainWindow\\\"         xmlns=\\\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\\\"         xmlns:x=\\\"http://schemas.microsoft.com/winfx/2006/xaml\\\"         xmlns:d=\\\"http://schemas.microsoft.com/expression/blend/2008\\\"         xmlns:mc=\\\"http://schemas.openxmlformats.org/markup-compatibili

    2024年04月22日
    浏览(39)
  • C# WPF 数据绑定

    后台变量发生改变,前端对应的相关属性值也发生改变 接口 INotifyPropertyChanged 用于通知客户端(通常绑定客户端)属性值已更改。 官方示例代码如下 示例演示 before after 本示例提供了多种绑定方式,使用接口进行绑定,不使用接口进行绑定 1.在MainWindow中进行属性更改 2.在

    2024年02月02日
    浏览(45)
  • C# WPF编程-布局

    WPF窗口只能包含单个元素。为在WPF窗口中放置多个元素并创建更贴近实用的用户界面,需要在窗口上放置一个容器,然后在这个容器中添加其他元素。 造成这一限制的原因是Window类继承自ContentControl类。 在WPF窗口布局需要遵循以下几条重要原则: 不应显示设定元素的尺寸 :

    2024年03月25日
    浏览(39)
  • C# wpf程序

    --App.xaml namespace WpfMyproject {     /// summary     /// App.xaml 的交互逻辑     /// /summary     public partial class App : PrismApplication     {         protected override Window CreateShell()         {             return Container.ResolveMainView();         }         protected override void RegisterTypes(IContainer

    2024年02月14日
    浏览(45)
  • WPF动画如何使用?炫酷的WPF UI必须掌握,赶紧学起来

    使用WPF,可以轻松地创建复杂的图形和动画,实现各种交互效果,以及使用各种不同的数据绑定和样式。无论你是初学者还是有经验的开发人员,都可以通过学习WPF来开发具有创新性和吸引力的应用程序。赶紧学起来吧! 在WPF中,Storyboard是一种非常有用的工具,它主要用于创

    2024年02月15日
    浏览(44)
  • 【Dotnet 工具箱】WPF UI - 现代化设计的开源 WPF 框架

    WPF UI 是一个基于 C# 开发的, 拥有 4k star 的开源 UI 框架。WPF UI 在 WPF 的基础上,提供了更多的现代化,流利的,直观的设计和组件。重要的是,WPF UI 完全免费! 如果你对 WPF 比较熟悉,那么可以很快的上手这个 UI 框架,并集成中项目中去。WPF UI 提供了完善的使用文档,对新

    2023年04月19日
    浏览(56)
  • c# wpf Template ContentTemplate

    1.概要 1.1 定义内容的外观 2.2 要点分析 2.代码 3.运行结果

    2024年04月09日
    浏览(40)
  • c# wpf MultiTrigger 简单试验

    1.概要 2.代码 3.试验结果

    2024年04月10日
    浏览(31)
  • c# wpf XmlDataProvider 简单试验

    1.概要 2.代码 3.试验结果

    2024年04月10日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包