打造WPF时间管理应用:HandyControl的Calendar控件全攻略

打造WPF时间管理应用:HandyControl的Calendar控件全攻略

【免费下载链接】HandyControl Contains some simple and commonly used WPF controls 【免费下载链接】HandyControl 项目地址: https://gitcode.com/gh_mirrors/ha/HandyControl

你是否还在为WPF应用中的日期选择功能开发而烦恼?传统Calendar控件样式单一、交互生硬,无法满足现代UI设计需求。本文将带你深入探索HandyControl的Calendar控件体系,通过实战案例演示如何构建一个功能完备的时间管理应用,从基础集成到高级定制,全程代码驱动,让你的应用瞬间拥有专业级日期交互体验。

读完本文你将掌握:

  • Calendar与CalendarWithClock控件的核心差异及应用场景
  • 日期选择、范围限制与自定义格式化的实现方案
  • MVVM模式下的数据绑定与事件处理技巧
  • 10分钟上手的时间管理应用完整开发流程

HandyControl日历控件体系解析

HandyControl提供了两套日历解决方案,分别满足不同场景需求。通过理解其架构设计,可以帮助我们在实际项目中做出最优选择。

核心控件对比

控件类型继承关系核心特性适用场景
Calendar继承自原生WPF Calendar基础日期选择,支持月份视图切换简单日期选择场景,如生日录入
CalendarWithClock自定义Control整合时钟组件,支持精确到秒的时间选择日程安排、任务管理等需精确时间的场景

mermaid

关键依赖属性解析

CalendarWithClock作为增强型控件,提供了丰富的自定义属性:

// 日期时间格式化字符串
public static readonly DependencyProperty DateTimeFormatProperty = 
    DependencyProperty.Register(nameof(DateTimeFormat), typeof(string), 
    typeof(CalendarWithClock), new PropertyMetadata("yyyy-MM-dd HH:mm:ss"));

// 确认按钮显示状态
public static readonly DependencyProperty ShowConfirmButtonProperty = 
    DependencyProperty.Register(nameof(ShowConfirmButton), typeof(bool), 
    typeof(CalendarWithClock), new PropertyMetadata(ValueBoxes.FalseBox));

// 选中的日期时间
public static readonly DependencyProperty SelectedDateTimeProperty = 
    DependencyProperty.Register(nameof(SelectedDateTime), typeof(DateTime?), 
    typeof(CalendarWithClock), new PropertyMetadata(default(DateTime?), OnSelectedDateTimeChanged));

这些属性允许开发者通过XAML直接配置控件行为,无需编写大量后台代码。

快速集成:从0到1的日历控件应用

基础Calendar控件使用

原生Calendar控件提供了基础的日期选择功能,通过HandyControl的样式增强,拥有了现代化的视觉表现:

<Window 
    xmlns:hc="https://handyorg.github.io/handycontrol"
    xmlns:sys="clr-namespace:System;assembly=mscorlib">
    
    <hc:Window.Resources>
        <!-- 自定义日历样式 -->
        <Style x:Key="CustomCalendarStyle" BasedOn="{StaticResource CalendarBaseStyle}" TargetType="Calendar">
            <Setter Property="Foreground" Value="#333333"/>
            <Setter Property="Background" Value="#F5F5F5"/>
            <Setter Property="BorderBrush" Value="#E0E0E0"/>
        </Style>
    </hc:Window.Resources>
    
    <!-- 基础日历控件 -->
    <Calendar 
        Style="{StaticResource CustomCalendarStyle}"
        Margin="20"
        DisplayDate="2025-09-01"
        SelectedDate="{Binding SelectedTaskDate, Mode=TwoWay}">
        <!-- 禁用过去日期 -->
        <Calendar.BlackoutDates>
            <CalendarDateRange Start="1/1/1900" End="{x:Static sys:DateTime.Now.AddDays(-1)}"/>
        </Calendar.BlackoutDates>
    </Calendar>
</Window>

上述代码实现了一个具有以下特性的日历:

  • 应用HandyControl基础样式并自定义外观
  • 默认显示2025年9月
  • 禁用所有过去日期
  • 与ViewModel的SelectedTaskDate属性双向绑定

CalendarWithClock高级应用

对于需要精确到时间的场景,CalendarWithClock是更优选择。以下是一个包含确认按钮的时间选择器实现:

<hc:CalendarWithClock
    x:Name="taskCalendar"
    DateTimeFormat="yyyy年MM月dd日 HH:mm"
    ShowConfirmButton="True"
    SelectedDateTime="{Binding TaskDateTime, Mode=TwoWay}"
    DisplayDateTimeChanged="CalendarWithClock_DisplayDateTimeChanged"
    Margin="10"/>

后台代码处理:

private void CalendarWithClock_DisplayDateTimeChanged(object sender, FunctionEventArgs<DateTime> e)
{
    // 实时更新预览文本
    previewTextBlock.Text = e.Info.ToString("yyyy年MM月dd日 HH:mm");
    
    // 业务逻辑验证:禁止选择周末
    if (e.Info.DayOfWeek == DayOfWeek.Saturday || e.Info.DayOfWeek == DayOfWeek.Sunday)
    {
        HintProxy.SetHint(taskCalendar, "任务不能安排在周末");
        taskCalendar.BorderBrush = Brushes.Red;
    }
    else
    {
        HintProxy.SetHint(taskCalendar, null);
        taskCalendar.BorderBrush = Brushes.Gray;
    }
}

通过DisplayDateTimeChanged事件,我们可以实现实时验证和反馈,提升用户体验。

时间管理应用实战开发

基于CalendarWithClock控件,我们将构建一个迷你时间管理应用,实现任务的创建、编辑和日历视图展示功能。该应用采用MVVM架构,确保代码的可维护性和可测试性。

项目结构设计

TimeManager/
├─ View/
│  ├─ TaskCalendarView.xaml      # 日历视图
│  ├─ TaskDetailView.xaml        # 任务详情编辑
│  └─ MainWindow.xaml            # 主窗口
├─ ViewModel/
│  ├─ TaskViewModel.cs           # 任务数据模型
│  ├─ CalendarViewModel.cs       # 日历视图模型
│  └─ MainViewModel.cs           # 主窗口视图模型
└─ Model/
   └─ TaskModel.cs               # 任务实体类

核心功能实现

1. 任务实体定义
public class TaskModel : ObservableObject
{
    private string _title;
    private DateTime _dueDateTime;
    private bool _isCompleted;
    
    public string Title 
    { 
        get => _title; 
        set => SetProperty(ref _title, value); 
    }
    
    public DateTime DueDateTime 
    { 
        get => _dueDateTime; 
        set => SetProperty(ref _dueDateTime, value); 
    }
    
    public bool IsCompleted 
    { 
        get => _isCompleted; 
        set => SetProperty(ref _isCompleted, value); 
    }
    
    // 任务优先级枚举
    public enum PriorityLevel { Low, Medium, High }
    public PriorityLevel Priority { get; set; }
}
2. 日历视图模型
public class CalendarViewModel : ViewModelBase
{
    private ObservableCollection<TaskModel> _tasks;
    private DateTime? _selectedDate;
    private ICommand _addTaskCommand;
    
    public CalendarViewModel()
    {
        _tasks = new ObservableCollection<TaskModel>();
        _addTaskCommand = new RelayCommand(AddTask);
        
        // 示例数据
        _tasks.Add(new TaskModel 
        { 
            Title = "项目需求评审", 
            DueDateTime = DateTime.Now.AddDays(2).AddHours(14),
            Priority = TaskModel.PriorityLevel.High
        });
    }
    
    public ObservableCollection<TaskModel> Tasks => _tasks;
    
    public DateTime? SelectedDate 
    { 
        get => _selectedDate; 
        set => SetProperty(ref _selectedDate, value); 
    }
    
    public ICommand AddTaskCommand => _addTaskCommand;
    
    private void AddTask()
    {
        if (SelectedDate.HasValue)
        {
            Tasks.Add(new TaskModel 
            { 
                Title = "新任务",
                DueDateTime = SelectedDate.Value.AddHours(10),
                Priority = TaskModel.PriorityLevel.Medium
            });
        }
    }
    
    // 根据日期获取任务
    public IEnumerable<TaskModel> GetTasksForDate(DateTime date)
    {
        return Tasks.Where(t => 
            t.DueDateTime.Date == date.Date && !t.IsCompleted);
    }
}
3. 日历视图XAML实现
<UserControl x:Class="TimeManager.View.TaskCalendarView"
             xmlns:hc="https://handyorg.github.io/handycontrol">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        
        <!-- 日历控件 -->
        <hc:CalendarWithClock
            Grid.Row="0"
            SelectedDateTime="{Binding SelectedDate, Mode=TwoWay}"
            DateTimeFormat="yyyy-MM-dd HH:mm"
            ShowConfirmButton="True"
            Margin="10">
            <!-- 自定义日历项模板 -->
            <hc:CalendarWithClock.CalendarStyle>
                <Style BasedOn="{StaticResource CalendarBaseStyle}" TargetType="Calendar">
                    <Setter Property="CalendarItemTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <Border BorderThickness="1" BorderBrush="#E0E0E0">
                                    <StackPanel>
                                        <TextBlock Text="{Binding Date, StringFormat='{}{0:dd}'}" 
                                                   HorizontalAlignment="Center"/>
                                        <!-- 任务标记 -->
                                        <ItemsControl ItemsSource="{Binding DataContext.GetTasksForDate(Date), 
                                            RelativeSource={RelativeSource AncestorType=UserControl}}">
                                            <ItemsControl.ItemTemplate>
                                                <DataTemplate>
                                                    <Border Background="{Binding Priority, 
                                                        Converter={StaticResource PriorityToBrushConverter}}"
                                                            CornerRadius="3" Margin="1" Height="6"/>
                                                </DataTemplate>
                                            </ItemsControl.ItemTemplate>
                                        </ItemsControl>
                                    </StackPanel>
                                </Border>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </hc:CalendarWithClock.CalendarStyle>
        </hc:CalendarWithClock>
        
        <!-- 添加任务按钮 -->
        <Button Grid.Row="1" 
                Command="{Binding AddTaskCommand}"
                Content="添加任务"
                Style="{StaticResource PrimaryButtonStyle}"
                Margin="10" Width="120"/>
    </Grid>
</UserControl>
4. 优先级转颜色转换器
public class PriorityToBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value switch
        {
            TaskModel.PriorityLevel.High => new SolidColorBrush(Color.FromRgb(245, 108, 108)),
            TaskModel.PriorityLevel.Medium => new SolidColorBrush(Color.FromRgb(250, 173, 20)),
            _ => new SolidColorBrush(Color.FromRgb(103, 194, 58))
        };
    }
    
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

数据绑定与事件处理

在ViewModel中处理日期选择变更:

public class CalendarViewModel : ViewModelBase
{
    private DateTime? _selectedDate;
    
    public DateTime? SelectedDate 
    { 
        get => _selectedDate; 
        set 
        {
            if (SetProperty(ref _selectedDate, value) && value.HasValue)
            {
                // 当选择日期变更时,加载当天任务
                LoadTasksForDate(value.Value);
            }
        } 
    }
    
    private void LoadTasksForDate(DateTime date)
    {
        // 从数据服务加载任务
        var tasks = _taskDataService.GetTasksByDate(date);
        Tasks.Clear();
        foreach (var task in tasks)
        {
            Tasks.Add(new TaskViewModel(task));
        }
    }
}

高级定制与性能优化

为了满足复杂业务需求,HandyControl日历控件支持深度定制。以下是几个实用的高级技巧,可以帮助你打造更具特色的日期交互体验。

自定义日期范围限制

通过BlackoutDates属性可以限制用户可选择的日期范围,适用于如"只能选择未来7天内日期"的业务场景:

// 在ViewModel中定义日期范围
public CalendarDateRangeCollection AllowedDates { get; } = new CalendarDateRangeCollection();

// 初始化允许选择的日期范围
private void InitDateRestrictions()
{
    // 清除所有限制
    AllowedDates.Clear();
    
    // 添加允许选择的日期范围:今天到未来7天
    var startDate = DateTime.Now;
    var endDate = DateTime.Now.AddDays(7);
    AllowedDates.Add(new CalendarDateRange(startDate, endDate));
    
    // 添加特定禁止日期(如节假日)
    var holidays = new List<DateTime>
    {
        new DateTime(2025, 10, 1),  // 国庆节
        new DateTime(2025, 10, 2),
        new DateTime(2025, 10, 3)
    };
    
    foreach (var holiday in holidays)
    {
        AllowedDates.Add(new CalendarDateRange(holiday, holiday) { Start = holiday, End = holiday });
        AllowedDates[AllowedDates.Count - 1].ApplyTo = CalendarDateRangeType.Blackout;
    }
}

多日期选择模式

通过设置SelectionMode属性,可以实现单选、多选或范围选择模式:

<!-- 范围选择模式 -->
<Calendar SelectionMode="Range"
          SelectedDatesChanged="Calendar_SelectedDatesChanged"/>

后台事件处理:

private void Calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
    var calendar = sender as Calendar;
    if (calendar?.SelectedDates != null && calendar.SelectedDates.Count >= 2)
    {
        var startDate = calendar.SelectedDates.Min();
        var endDate = calendar.SelectedDates.Max();
        var daysCount = (endDate - startDate).Days + 1;
        
        // 显示选择的日期范围信息
        _viewModel.SelectedDateRange = $"{startDate:yyyy-MM-dd} 至 {endDate:yyyy-MM-dd}(共{daysCount}天)";
    }
}

性能优化策略

当处理大量任务数据时,日历控件可能会出现渲染性能问题。以下是几个优化建议:

  1. UI虚拟化:确保ItemsControl启用虚拟化以处理大量数据项
<ItemsControl VirtualizingStackPanel.IsVirtualizing="True"
              VirtualizingStackPanel.VirtualizationMode="Recycling">
    <!-- 项模板 -->
</ItemsControl>
  1. 数据缓存:在ViewModel中缓存已加载的日期数据,避免重复查询
// 使用字典缓存日期对应的任务数据
private Dictionary<DateTime, List<TaskModel>> _taskCache = new Dictionary<DateTime, List<TaskModel>>();

public IEnumerable<TaskModel> GetTasksForDate(DateTime date)
{
    if (_taskCache.TryGetValue(date.Date, out var cachedTasks))
    {
        return cachedTasks;
    }
    
    // 从数据库加载数据
    var tasks = _dataService.GetTasksByDate(date).ToList();
    _taskCache[date.Date] = tasks;
    
    return tasks;
}
  1. 延迟加载:仅加载当前可见日期范围内的任务数据
// 监听日历显示日期变更事件
private void Calendar_DisplayDateChanged(object sender, CalendarDateChangedEventArgs e)
{
    // 获取当前显示的月份
    var visibleMonth = e.NewDate.Month;
    var visibleYear = e.NewDate.Year;
    
    // 加载该月份的所有任务
    _viewModel.LoadTasksForMonth(visibleYear, visibleMonth);
}

部署与扩展指南

完成开发后,我们需要将应用程序部署到用户环境。HandyControl提供了多种部署选项,可根据项目需求选择最合适的方式。

控件集成方式

HandyControl支持两种集成方式,可根据项目规模选择:

  1. NuGet包引用(推荐):
Install-Package HandyControl -Version 3.4.0
  1. 源码集成:克隆仓库后直接引用项目文件
git clone https://gitcode.com/gh_mirrors/ha/HandyControl.git

常见问题解决方案

日期格式显示异常

问题:SelectedDateTime绑定后显示格式不符合预期
解决:通过StringFormat显式指定格式或使用DateTimeFormat属性

<!-- 方式一:使用StringFormat -->
<TextBlock Text="{Binding SelectedDateTime, StringFormat='yyyy年MM月dd日 HH:mm'}"/>

<!-- 方式二:设置控件的DateTimeFormat -->
<hc:CalendarWithClock DateTimeFormat="yyyy年MM月dd日 HH:mm"/>
MVVM模式下事件处理

问题:需要在ViewModel中处理控件事件
解决:使用EventToCommand行为将事件转换为命令

<hc:CalendarWithClock>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectedDateTimeChanged">
            <hc:EventToCommand Command="{Binding DateSelectedCommand}"
                               PassEventArgsToCommand="True"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</hc:CalendarWithClock>

ViewModel中命令定义:

public ICommand DateSelectedCommand { get; }

// 构造函数中初始化
DateSelectedCommand = new RelayCommand<FunctionEventArgs<DateTime?>>(OnDateSelected);

private void OnDateSelected(FunctionEventArgs<DateTime?> e)
{
    if (e?.Info.HasValue == true)
    {
        // 处理选中的日期时间
        SelectedDate = e.Info.Value;
    }
}

项目实战总结与展望

通过本文的学习,我们掌握了HandyControl日历控件的核心用法,并基于此构建了一个功能完备的时间管理应用。该应用不仅实现了基础的日期选择功能,还通过MVVM架构实现了任务管理的完整业务流程。

功能回顾

  • ✅ 日期时间精确选择(精确到秒)
  • ✅ 任务创建、编辑与状态管理
  • ✅ 日历视图与任务可视化
  • ✅ 日期范围限制与验证
  • ✅ MVVM架构设计与数据绑定

后续扩展方向

  1. 议程视图:实现日/周/月视图切换,支持拖拽调整任务时间
  2. 提醒功能:整合系统通知,实现任务到期提醒
  3. 数据同步:添加云同步功能,支持多设备数据共享
  4. 统计分析:基于任务完成情况生成 productivity 报告

HandyControl作为一个活跃维护的开源项目,其控件库在不断更新迭代。建议定期关注官方仓库,及时获取新功能和性能改进。

mermaid

通过合理利用HandyControl提供的控件,开发者可以显著减少UI开发工作量,将更多精力投入到核心业务逻辑实现上。希望本文的内容能够帮助你在WPF项目中更好地应用日历控件,打造出色的用户体验。

本文示例代码已开源,可通过以下地址获取完整项目: https://gitcode.com/gh_mirrors/ha/HandyControl

如果你觉得本文对你有帮助,请点赞收藏并关注作者,获取更多WPF开发技巧和最佳实践。

下期预告:《HandyControl数据可视化控件实战:打造交互式任务进度看板》

【免费下载链接】HandyControl Contains some simple and commonly used WPF controls 【免费下载链接】HandyControl 项目地址: https://gitcode.com/gh_mirrors/ha/HandyControl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值