作为一名长期深耕.NET技术栈的开发者,我最近完成了基于WPF的图书管理系统开发。这个项目不仅实现了基础的图书增删改查功能,更通过现代化的UI设计和合理的架构设计,展示了WPF在企业级应用开发中的强大潜力。系统采用MVVM模式开发,前后端完全分离,数据库使用SQL Server,适合作为WPF入门到进阶的学习案例。
这个系统最初是为本地图书馆设计的,但它的架构使其可以轻松扩展到学校、书店等场景。我在开发过程中积累了不少WPF实战经验,特别是在数据绑定、命令绑定和自定义控件方面,这些都会在本文中详细分享。源码已托管在GitHub,包含完整的解决方案和数据库脚本。
选择WPF而非WinForms主要基于以下几个考虑:
数据库选用SQL Server Express,因为:
采用经典的Model-View-ViewModel架构:
csharp复制// 典型ViewModel基类实现
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
视图与ViewModel通过DataContext绑定:
xml复制<Window.DataContext>
<local:BookManagementViewModel/>
</Window.DataContext>
系统分为以下层次:
图书实体模型设计:
csharp复制public class Book
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Title { get; set; }
[StringLength(50)]
public string Author { get; set; }
public string ISBN { get; set; }
public DateTime PublishDate { get; set; }
public int Stock { get; set; }
}
采用Repository模式进行数据访问:
csharp复制public interface IBookRepository
{
Task<IEnumerable<Book>> GetAllAsync();
Task<Book> GetByIdAsync(int id);
Task AddAsync(Book book);
Task UpdateAsync(Book book);
Task DeleteAsync(int id);
}
借阅记录实体包含关键业务逻辑:
csharp复制public class BorrowRecord
{
public int Id { get; set; }
public int BookId { get; set; }
public Book Book { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public DateTime BorrowDate { get; set; }
public DateTime? ReturnDate { get; set; }
public bool IsOverdue =>
ReturnDate == null &&
(DateTime.Now - BorrowDate).TotalDays > 30;
}
使用LiveCharts实现数据可视化:
xml复制<lvc:CartesianChart Series="{Binding BorrowStats}"
LegendLocation="Right">
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="Month" Labels="{Binding Months}"/>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
双向绑定的典型用法:
xml复制<TextBox Text="{Binding CurrentBook.Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
集合绑定与ObservableCollection:
csharp复制private ObservableCollection<Book> _books;
public ObservableCollection<Book> Books
{
get => _books;
set
{
_books = value;
OnPropertyChanged();
}
}
RelayCommand的典型实现:
csharp复制public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
public void Execute(object parameter) => _execute();
}
开发图书卡片控件:
xml复制<Style TargetType="{x:Type local:BookCard}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BookCard}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<TextBlock Text="{TemplateBinding Title}"
FontWeight="Bold"/>
<TextBlock Text="{TemplateBinding Author}"/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
使用EF Core Code First迁移:
bash复制dotnet ef migrations add InitialCreate
dotnet ef database update
连接字符串配置:
json复制{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=LibraryDB;Trusted_Connection=True;"
}
}
xml复制<ListView VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
<!-- 列表项模板 -->
</ListView>
csharp复制private async Task LoadBooksAsync()
{
IsLoading = true;
try {
Books = new ObservableCollection<Book>(
await _bookRepository.GetAllAsync());
}
finally {
IsLoading = false;
}
}
csharp复制public class DateToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return ((DateTime)value).ToString("yyyy-MM-dd");
}
}
xml复制<Window.Resources>
<local:DebugConverter x:Key="debugConverter"/>
</Window.Resources>
<TextBlock Text="{Binding Path, Converter={StaticResource debugConverter}}"/>
xml复制<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Languages/en-US.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
csharp复制services.AddTransient<IBookRepository, BookRepository>();
services.AddTransient<BookManagementViewModel>();
这个项目从设计到实现大约花费了3周时间,期间最大的收获是深入理解了WPF的数据绑定机制和MVVM模式的实践要点。对于想要学习WPF的开发者,我的建议是从简单的数据绑定开始,逐步过渡到命令绑定和自定义控件,最后再考虑架构设计。源码中我特意保留了开发过程中的各种注释,记录了每个关键决策的思考过程,希望能对读者有所帮助。