【问题标题】:UWP CalendarView update on commandUWP CalendarView 更新命令
【发布时间】:2017-02-07 11:52:19
【问题描述】:

我正在使用通用 Windows 平台构建 Windows Phone 10 应用程序。在我的应用程序中,我有一个标准的 CalendarView,我想在有事件的日期上显示密度颜色。这个想法是在页面加载后立即加载日历,发出 API 请求,并在成功检索数据后让 CalendarView 刷新它的 UI,以便调用 CalendarViewDayItemChanging 事件。从那里我可以为有事件的单元格设置我的密度颜色。

除了一个部件外,我几乎所有东西都正常工作。当日历第一次加载时,我将它的最小/最大日期范围设置为当前月份,这样我们一次只能看到一个月。这会导致日历的 UI 按预期刷新。但是,在我的 API 请求完成后,如果我尝试再次将最小/最大日期范围设置为相同的日期,那么日历不会刷新它的 UI。因此,我无法强制 CalendarView 刷新它的 UI。

我尝试调用 UpdateLayout,尝试重置最小/最大日期范围,并尝试将日历的 DataContext 绑定到后面的代码中的 ObservableCollection,该代码在我的数据更新时更新。这些都不起作用,我没有看到任何更新 UI 的方法。

我对 UWP 很陌生,所以不确定我做错了什么。我知道数据绑定的概念是 UWP 的重要组成部分,但我不确定如何将我的数据绑定到此 CalendarView 以便在我的数据刷新时刷新。有什么建议吗?

下面是我现在的代码的快速摘录。

XAML

<CalendarView 
    Name="Calendar"
    NumberOfWeeksInView="6"
    CalendarViewDayItemChanging="CalendarView_DayItemChanging"
    DataContext="{Binding CalendarDates}">
</CalendarView>

代码隐藏

namespace Pages
{
    public sealed partial class CalendarPage : BasePage
    {
        #region Private Variables

        private CalendarPageModel PageModel = new CalendarPageModel();
        private ObservableCollection<DateTime> CalendarDates;

        #endregion

        #region Constructor

        public CalendarPage()
        {
            this.InitializeComponent();
            CalendarDates = new ObservableCollection<DateTime>();
        }

        #endregion

        #region Events

        private void Page_Loaded(object sender, RoutedEventArgs args)
        {
            SetCalendarDateRange(); //NOTE: This is done here so that my UI consistantly shows the correct dates on the screen
            LoadData();
        }

        private void CalendarView_DayItemChanging(CalendarView sender, CalendarViewDayItemChangingEventArgs args)
        {
            if (!PageModel.DateHasEvent(args.Item.Date))
            {
                args.Item.SetDensityColors(null);
            }
            else
            {
                List<Color> colors = new List<Color>();
                Color? color = Application.Current.Resources["CalendarHasEventDensityColor"] as Color?;
                if (color != null)
                {
                    colors.Add((Color)color);
                }

                args.Item.SetDensityColors(colors);
            }
        }

        #endregion

        #region Data

        private void SetCalendarDateRange()
        {
            Calendar.MinDate = PageModel.StartDate;
            Calendar.MaxDate = PageModel.EndDate;
        }

        private async void LoadData()
        {
            // get data
            await PageModel.RefreshData(PageModel.StartDate, PageModel.EndDate);

            // force calendar to update
            //NOTE: This only works if the date range is actually different than what it's currently set to
            SetCalendarDateRange();

            //NOTE: I have tried to just manually add a date to my observable collection to see if it'll kick off the calendar refresh, but it doesn't
            CalendarDates.add(DateTime.Now);
        }

        #endregion
    }
}

【问题讨论】:

  • 当您更改 MinDate 和 MaxDate 但您没有完整的重现(CalendarPageModel 是哪里?)时,绝对可以让日历更新 UI,所以看不出问题出在哪里在你的代码中。 RefreshData 会发生什么?还有什么叫LoadData
  • 这些都不重要。它被抽象出来,因为我唯一的问题是如何在不更改最小/最大日期的情况下更新日历。您可以假设 loadData 和 refreshData 调用获取数据并将数据存储到 CalendarPageModel 类中。现在这个数据没有连接到 CalendarView。如果您愿意,我可以删除该代码。无论如何,问题是您如何刷新 CalendarView,这意味着调用我上面概述的导师,而不更改最小/最大日期?
  • 顺便说一句,对不起 Ruppe's。在手机上输入。
  • 我想我已经弄清楚了您的目标,但对原始问题感到困惑。将来,请注意stackoverflow.com/help/mcve 中记录的 repro 的值,因为这样可以避免由于引用未包含的代码而可能造成的混淆。

标签: c# calendar uwp windows-10-universal uwp-xaml


【解决方案1】:

坏消息
不幸的是,CalendarView 控件并不是为这种情况设计的。因为它针对显示大量天时的性能进行了优化,所以它只在加载单个天时刷新 UI。

不过……

好消息
可以修改控件以创建此行为,但需要做一些工作。

基本原则是负责绘制Density色块并将它们绑定到可以通过绑定更新的东西。

作为此工作的示例,将以下内容添加到页面中

<Page.Resources>
    <local:ColorBrushConverter x:Key="BrushConverter" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <StackPanel>
        <CalendarView Name="Calendar"
                      DisplayMode="Month"
                      CalendarViewDayItemChanging="CalendarView_DayItemChanging"
                      >
            <CalendarView.CalendarViewDayItemStyle>
                <Style TargetType="CalendarViewDayItem" >
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="CalendarViewDayItem">
                                <Grid Opacity="0.5">
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="*"/>
                                        <RowDefinition Height="*"/>
                                        <RowDefinition Height="*"/>
                                        <RowDefinition Height="*"/>
                                        <RowDefinition Height="*"/>
                                    </Grid.RowDefinitions>
                                    <Rectangle Grid.Row="0" Fill="{Binding FifthColor, Converter={StaticResource BrushConverter}}" />
                                    <Rectangle Grid.Row="1" Fill="{Binding FourthColor, Converter={StaticResource BrushConverter}}" />
                                    <Rectangle Grid.Row="2" Fill="{Binding ThirdColor, Converter={StaticResource BrushConverter}}" />
                                    <Rectangle Grid.Row="3" Fill="{Binding SecondColor, Converter={StaticResource BrushConverter}}" />
                                    <Rectangle Grid.Row="4" Fill="{Binding FirstColor, Converter={StaticResource BrushConverter}}" />
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </CalendarView.CalendarViewDayItemStyle>
        </CalendarView>
        <Button Click="AddEventClicked">Add random event</Button>
    </StackPanel>
</Grid>

以及后面的代码:

public sealed partial class MainPage : Page
{
    private MyViewModel ViewModel;

    private DateTime today;
    private DateTime minDate;
    private DateTimeOffset maxDate;

    public MainPage()
    {
        this.InitializeComponent();

        // Keep these for reference
        this.today = DateTime.Now.Date;
        this.minDate = new DateTime(today.Year, today.Month, 1);
        this.maxDate = minDate.AddMonths(1);

        // Create our viewmodel
        ViewModel = MyViewModel.Generate(minDate.Date, maxDate.Date);
        Calendar.MinDate = minDate;
        Calendar.MaxDate = maxDate;

        // Add data for the next three days - will be shown when page loads
        ViewModel.Dates[today.AddDays(1)].Add(Colors.Red);
        ViewModel.Dates[today.AddDays(2)].Add(Colors.Purple);
        ViewModel.Dates[today.AddDays(2)].Add(Colors.Blue);
        ViewModel.Dates[today.AddDays(3)].Add(Colors.Green);
    }

    private void CalendarView_DayItemChanging(CalendarView sender, CalendarViewDayItemChangingEventArgs args)
    {
        // When the DayItem in the calendar is loaded
        var itemDate = args?.Item?.Date.Date ?? DateTime.MinValue;

        if (ViewModel.Dates.ContainsKey(itemDate))
        {
            // Set the datacontext for our custom control
            // - Which does support 2way binding :)
            args.Item.DataContext = ViewModel.Dates[itemDate];
        }
    }

    private void AddEventClicked(object sender, RoutedEventArgs e)
    {
        var rand = new Random();
        var randomColor = Color.FromArgb(
                                         255,
                                         (byte) rand.Next(0, 254),
                                         (byte)rand.Next(0, 254),
                                         (byte)rand.Next(0, 254));

        var randomDay = rand.Next(1, 29);
        var randDateInMonth = new DateTime(today.Year, today.Month, randomDay);

        if (ViewModel.Dates.ContainsKey(randDateInMonth))
        {
            ViewModel.Dates[randDateInMonth].Add(randomColor);
        }
    }
}

public class MyViewModel
{
    // The VM really just holds this dictionary
    public Dictionary<DateTime, DensityColors> Dates { get; }

    private MyViewModel()
    {
        this.Dates = new Dictionary<DateTime, DensityColors>();
    }

    // Static constructor to limit the dates and populate dictionary
    public static MyViewModel Generate(DateTime minDate, DateTime maxDate)
    {
        var generated = new MyViewModel();

        for (var i = 0; i < (maxDate - minDate).TotalDays; i++)
        {
            generated.Dates.Add(minDate.AddDays(i), new DensityColors());
        }

        return generated;
    }
}

public class DensityColors : ObservableCollection<Color>, INotifyPropertyChanged
{
    // Properties that expose items in underlying OC
    public Color FirstColor => Items.Any() ? Items.First() : Colors.Transparent;
    public Color SecondColor => Items.Count > 1 ? Items.Skip(1).First() : Colors.Transparent;
    public Color ThirdColor => Items.Count > 2 ? Items.Skip(2).First() : Colors.Transparent;
    public Color FourthColor => Items.Count > 3 ? Items.Skip(3).First() : Colors.Transparent;
    public Color FifthColor => Items.Count > 4 ? Items.Skip(4).First() : Colors.Transparent;

    protected override void InsertItem(int index, Color item)
    {
        base.InsertItem(index, item);

        // Hacky forcing of updating UI for all properties
        OnPropertyChanged(nameof(FirstColor));
        OnPropertyChanged(nameof(SecondColor));
        OnPropertyChanged(nameof(ThirdColor));
        OnPropertyChanged(nameof(FourthColor));
        OnPropertyChanged(nameof(FifthColor));
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class ColorBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value is Color)
        {
            return new SolidColorBrush((Color)value);
        }

        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

这限制为每天 5 个条目(不像内置控件那样 10 个,任何更多都被忽略)但应该让您了解如何实现您所追求的或根据需要进行修改。

【讨论】:

  • 感谢 Matt 的帮助。这看起来像是一个可行的解决方案,因此会将其标记为已接受的答案。但是,我只是破解了一种方法来解决我的问题。基本上,我在初始页面加载时隐藏日历,然后在我第一次加载数据后,我强制我的日期范围为当月,然后显示日历。从那时起,数据仅在日期范围更新时才会更新,因此 CalendarViewDayItemChanging 开始发挥作用并允许我基于此进行更新。有点hacky,但它有效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-10-16
  • 1970-01-01
  • 1970-01-01
  • 2011-01-05
  • 2017-05-09
  • 1970-01-01
  • 2018-09-09
相关资源
最近更新 更多