2
\$\begingroup\$

I have a calendar view that is slow in uploading. The part that slows down the calendarview should be in the CalendarPageViewModel.cs. But I do not know how to improve it because it's a loop for.

Whole project Calendar View.

CalendarPicker.xaml:

<UserControl
x:Class="CalendarView.CalendarPicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:CalendarView"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Loaded="Page_Loaded"
Height="500"
Width="350">

<UserControl.DataContext>
    <local:CalendarPageViewModel x:Name="ViewModel"/>
</UserControl.DataContext>

<Grid>
    <Grid Background="#FF106F97">
        <Grid Width="350" Height="500" HorizontalAlignment="Center" VerticalAlignment="Bottom">
            <Grid.RowDefinitions>
                <RowDefinition Height="50"/>
                <RowDefinition Height="50"/>
                <RowDefinition Height="50"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Button x:Name="btnToday" Click="btnToday_Click" FontSize="20" Foreground="White" VerticalAlignment="Stretch" Style="{StaticResource ButtonStyleToday}" Padding="4" HorizontalAlignment="Stretch"/>
            <TextBlock x:Name="txtSelectedDay" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="12,0,0,0" FontSize="20" Foreground="White"/>
            <Button x:Name="btnMonthYear" Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" FontSize="25" Foreground="White"  Margin="12,0,0,0" Style="{StaticResource ButtonStyleTrasparent}" Padding="0,4,8,4"/>
            <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Height="50" VerticalAlignment="Center">
                <Button x:Name="btnDecYearMonthDay" Click="btnDecYearMonthDay_Click" Content="&#xE010;" FontFamily="Segoe MDL2 Assets" VerticalAlignment="Stretch" Width="50" FontSize="30" Padding="0" BorderBrush="White" Foreground="White" BorderThickness="0" Style="{StaticResource ButtonStyleTrasparent}"/>
                <Button x:Name="btnIncYearMonthDay" Click="btnIncYearMonthDay_Click" Content="&#xE011;" FontFamily="Segoe MDL2 Assets" VerticalAlignment="Stretch" Width="50" FontSize="30" Padding="0" BorderBrush="White" Foreground="White" BorderThickness="0" Style="{StaticResource ButtonStyleTrasparent}"/>
            </StackPanel>
            <Grid x:Name="BaseGridDays" Visibility="Visible" Grid.Row="3" RenderTransformOrigin="0.5,0.5">
                <Grid.RenderTransform>
                    <CompositeTransform/>
                </Grid.RenderTransform>
                <Grid x:Name="GridHeadingDay" Height="50" VerticalAlignment="Top">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Column="0" Text="lu" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" FontSize="25"/>
                    <TextBlock Grid.Column="1" Text="ma" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" FontSize="25"/>
                    <TextBlock Grid.Column="2" Text="me" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" FontSize="25"/>
                    <TextBlock Grid.Column="3" Text="gi" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" FontSize="25"/>
                    <TextBlock Grid.Column="4" Text="ve" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" FontSize="25"/>
                    <TextBlock Grid.Column="5" Text="sa" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" FontSize="25"/>
                    <TextBlock Grid.Column="6" Text="do" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" FontSize="25"/>
                </Grid>
                <Canvas x:Name="CanvasRootDays" Height="300" VerticalAlignment="Bottom">
                    <GridView x:Name="GridDays" ItemsSource="{Binding ItemsDay}" IsItemClickEnabled="True" ItemClick="GridDays_ItemClick" Height="300" Width="350" Padding="0" IsSwipeEnabled="False" IsTabStop="False" IsSynchronizedWithCurrentItem="False" ItemContainerStyle="{StaticResource GridViewItemStyle}" Style="{StaticResource GridViewStyleAnniMesiGiorni}" ScrollViewer.VerticalScrollBarVisibility="Hidden">
                        <GridView.ItemTemplate>
                            <DataTemplate x:DataType="local:CalendarDay">
                                <Grid Width="50" Height="50" Background="{x:Bind ToDayColor}">
                                    <TextBlock Text="{x:Bind ProgressiveDay, Mode=OneWay}" Foreground="{x:Bind daysColor, Mode=OneWay}" FontSize="15" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                                </Grid>
                            </DataTemplate>
                        </GridView.ItemTemplate>
                    </GridView>
                </Canvas>
            </Grid>
        </Grid>
    </Grid>
</Grid>

CalendarPicker.xaml.cs:

public sealed partial class CalendarPicker : UserControl
{
    DispatcherTimer timerVisDays = new DispatcherTimer();
    DispatcherTimer timerTimeNow = new DispatcherTimer();
    int FirstDay;

    public CalendarPicker()
    {
        InitializeComponent();
        timerVisDays.Interval = new TimeSpan(0, 0, 0, 0, 30);
        timerVisDays.Tick += timerShowDays_tick;
        timerVisDays.Start();
    }

    private void GridDays_ItemClick(object sender, ItemClickEventArgs e)
    {
        var item = e.ClickedItem as CalendarDay;
        var index = (ViewModel.ItemsDay).IndexOf(item);
        string dayString = FindStringWeek(Convert.ToUInt16(ViewModel.listDateDay[index - 1].DayOfWeek));
        int day = ViewModel.listDateDay[index].Day;
        string month = FindStringMonth(ViewModel.listDateDay[index].Month);
        int year = ViewModel.listDateDay[index].Year;
        txtSelectedDay.Text = dayString + "  " + day.ToString() + "  " + month + "  " + year.ToString();
    }

    private void timerShowDays_tick(object sender, object e)
    {
        ScrollViewer listScrollViewerDays = GetScrollViewer(GridDays);
        FirstDay = (Convert.ToUInt16(Math.Truncate(listScrollViewerDays.VerticalOffset / 50)) * 7);
        int numberOfDates;
        double remnant = (listScrollViewerDays.VerticalOffset / 50) - (Math.Truncate(listScrollViewerDays.VerticalOffset / 50));
        if (remnant == 0)
        {
            numberOfDates = 42;
        }
        else
        {
            numberOfDates = 49;
        }
        int nDaysMonthFirstDate = FindNumberOfDays(ViewModel.listDateDay[FirstDay].Month);
        int firstDayPresent = ViewModel.listDateDay[FirstDay].Day;
        int nDaysPresentFirstDate = nDaysMonthFirstDate - (firstDayPresent - 1);
        int nDaysPresentSecondDate = numberOfDates - nDaysPresentFirstDate;
        if (nDaysPresentFirstDate > nDaysPresentSecondDate)
        {
            for (int a = FirstDay; a <= FirstDay + (numberOfDates - 1); a++)
            {
                if (a <= ViewModel.ItemsDay.Count - 1)
                {
                    if (ViewModel.listDateDay[a].Month != ViewModel.listDateDay[FirstDay].Month)
                    {
                        ViewModel.ItemsDay[a].daysColor = new SolidColorBrush(Color.FromArgb(255, 160, 160, 160));
                    }
                }
            }
        }
        else
        {
            for (int a = FirstDay; a <= FirstDay + (numberOfDates - 1); a++)
            {
                if (a <= ViewModel.ItemsDay.Count - 1)
                {
                    if (ViewModel.listDateDay[a].Month != ViewModel.listDateDay[FirstDay].AddMonths(1).Month)
                    {
                        ViewModel.ItemsDay[a].daysColor = new SolidColorBrush(Color.FromArgb(255, 160, 160, 160));
                    }
                }
            }
        }
        timerVisDays.Stop();
    }

    private string FindStringWeek(int gs)
    {
        if (true == (gs == 0))
        {
            return "monday";
        }
        else if (true == (gs == 1))
        {
            return "tuesday";
        }
        else if (true == (gs == 2))
        {
            return "wednesday";
        }
        else if (true == (gs == 3))
        {
            return "thursday";
        }
        else if (true == (gs == 4))
        {
            return "friday";
        }
        else if (true == (gs == 5))
        {
            return "saturday";
        }
        else if (true == (gs == 6))
        {
            return "sunday";
        }
        else
        {
            return "";
        }
    }

    private string FindStringMonth(int gs)
    {
        if (true == (gs == 1))
        {
            return "ganuary";
        }
        else if (true == (gs == 2))
        {
            return "february";
        }
        else if (true == (gs == 3))
        {
            return "march";
        }
        else if (true == (gs == 4))
        {
            return "april";
        }
        else if (true == (gs == 5))
        {
            return "may";
        }
        else if (true == (gs == 6))
        {
            return "june";
        }
        else if (true == (gs == 7))
        {
            return "july";
        }
        else if (true == (gs == 8))
        {
            return "august";
        }
        else if (true == (gs == 9))
        {
            return "september";
        }
        else if (true == (gs == 10))
        {
            return "october";
        }
        else if (true == (gs == 11))
        {
            return "november";
        }
        else if (true == (gs == 12))
        {
            return "december";
        }
        else
        {
            return "";
        }
    }

    private int FindNumberOfDays(int gs)
    {
        if (true == (gs == 1))
        {
            return 31;
        }
        else if (true == (gs == 2))
        {
            return 29;
        }
        else if (true == (gs == 3))
        {
            return 31;
        }
        else if (true == (gs == 4))
        {
            return 30;
        }
        else if (true == (gs == 5))
        {
            return 31;
        }
        else if (true == (gs == 6))
        {
            return 30;
        }
        else if (true == (gs == 7))
        {
            return 31;
        }
        else if (true == (gs == 8))
        {
            return 31;
        }
        else if (true == (gs == 9))
        {
            return 30;
        }
        else if (true == (gs == 10))
        {
            return 31;
        }
        else if (true == (gs == 11))
        {
            return 30;
        }
        else if (true == (gs == 12))
        {
            return 31;
        }
        else
        {
            return 0;
        }
    }

    public ScrollViewer GetScrollViewer(DependencyObject o)
    {
        if (o is ScrollViewer)
        {
            return o as ScrollViewer;
        }

        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
        {
            var child = VisualTreeHelper.GetChild(o, i);
            var result = GetScrollViewer(child);
            if (result == null)
            {
                continue;
            }
            else
            {
                return result;
            }
        }
        return null;
    }

    private async void Page_Loaded(object sender, RoutedEventArgs e)
    {
        ScrollViewer listScrollViewerDays = GetScrollViewer(GridDays);
        GridDays.SelectedItem = GridDays.Items[ViewModel.indexDDay];
        ViewModel.ItemsDay[ViewModel.indexDDay].ToDayColor = new SolidColorBrush(Color.FromArgb(255, 17, 95, 155));
        await Task.Delay(100);
        GridDays.ScrollIntoView(GridDays.Items[ViewModel.indexToDay], ScrollIntoViewAlignment.Leading);
        listScrollViewerDays.ViewChanged += DisplayDays;
        btnToday.Content = FindStringWeek(Convert.ToInt32(DateTime.Now.DayOfWeek)) + " " + DateTime.Now.Day.ToString() + " " + FindStringMonth(DateTime.Now.Month) + " " + DateTime.Now.Year.ToString();
    }

    private void DisplayDays(object sender, ScrollViewerViewChangedEventArgs e)
    {
        timerVisDays.Stop();
        timerVisDays.Start();

        ScrollViewer listScrollViewer = GetScrollViewer(GridDays);
        FirstDay = (Convert.ToUInt16(Math.Truncate(listScrollViewer.VerticalOffset / 50)) * 7);
        int numberOfDates;
        string currentMonth = "";
        string currentYear = "";
        int indexCorrentMonth;
        double remnant = (listScrollViewer.VerticalOffset / 50) - (Math.Truncate(listScrollViewer.VerticalOffset / 50));
        if (remnant == 0)
        {
            numberOfDates = 42;
        }
        else
        {
            numberOfDates = 49;
        }
        int nDaysMonthFirstDate = FindNumberOfDays(ViewModel.listDateDay[FirstDay].Month);
        int firstDayPresent = ViewModel.listDateDay[FirstDay].Day;
        int nDaysPresentFirstDate = nDaysMonthFirstDate - (firstDayPresent - 1);
        int nDaysPresentSecondDate = numberOfDates - (nDaysPresentFirstDate);
        if (nDaysPresentFirstDate > nDaysPresentSecondDate)
        {
            currentMonth = FindStringMonth(ViewModel.listDateDay[FirstDay].Month);
            currentYear = ViewModel.listDateDay[FirstDay].Year.ToString();
            indexCorrentMonth = FirstDay;
        }
        else
        {
            int numMonth = ViewModel.listDateDay[FirstDay].AddMonths(1).Month;
            currentYear = ViewModel.listDateDay[FirstDay].AddMonths(1).Year.ToString();
            currentMonth = FindStringMonth(numMonth);

        }
        btnMonthYear.Content = currentMonth + " " + currentYear;
        for (int a = FirstDay; a <= FirstDay + numberOfDates; a++)
        {
            try
            {
                ViewModel.ItemsDay[a].daysColor = new SolidColorBrush(Colors.White);
            }
            catch { }
        }
    }

    private async void IncreasesMonth()
    {
        ScrollViewer listScrollViewer = GetScrollViewer(GridDays);
        FirstDay = (Convert.ToUInt16(Math.Truncate(listScrollViewer.VerticalOffset / 50)) * 7);
        int numberOfDates;
        bool firstdayfound = false;
        DateTime indexSucceedingMonth;
        double remnant = (listScrollViewer.VerticalOffset / 50) - (Math.Truncate(listScrollViewer.VerticalOffset / 50));
        if (remnant == 0)
        {
            numberOfDates = 42;
        }
        else
        {
            numberOfDates = 49;
        }
        int nDaysMonthFirstDate = FindNumberOfDays(ViewModel.listDateDay[FirstDay].Month);
        int firstDayPresent = ViewModel.listDateDay[FirstDay].Day;
        int nDaysPresentFirstDate = nDaysMonthFirstDate - (firstDayPresent - 1);
        int nDaysPresentSecondDate = numberOfDates - (nDaysPresentFirstDate);
        if (nDaysPresentFirstDate > nDaysPresentSecondDate)
        {
            indexSucceedingMonth = new DateTime(ViewModel.listDateDay[FirstDay].Year, ViewModel.listDateDay[FirstDay].AddMonths(1).Month, 1, 0, 0, 0);
            while (firstdayfound == false)
            {
                if (ViewModel.listDateDay[FirstDay] == indexSucceedingMonth)
                {
                    firstdayfound = true;
                }
                FirstDay += 1;
            }
        }
        else
        {
            DateTime IncreasedDate = ViewModel.listDateDay[FirstDay].AddMonths(2);
            indexSucceedingMonth = new DateTime(IncreasedDate.Year, IncreasedDate.Month, 1, 0, 0, 0);
            while (firstdayfound == false)
            {
                if (ViewModel.listDateDay[FirstDay] == indexSucceedingMonth)
                {
                    firstdayfound = true;
                }
                FirstDay += 1;
            }
        }

        var item = GridDays.ContainerFromIndex(FirstDay - 1) as GridViewItem;
        var gt = item.TransformToVisual(CanvasRootDays);
        double offestCanvas = gt.TransformPoint(new Point(0, 0)).Y;
        listScrollViewer.ChangeView(null, listScrollViewer.VerticalOffset + offestCanvas, null, false);

        await Task.Delay(300);
    }

    private async void DecreaseMonth()
    {
        ScrollViewer listScrollViewer = GetScrollViewer(GridDays);
        FirstDay = (Convert.ToUInt16(Math.Truncate(listScrollViewer.VerticalOffset / 50)) * 7);
        int numberOfDates;
        bool firstdayfound = false;
        DateTime indexSucceedingMonth;
        double avanzo = (listScrollViewer.VerticalOffset / 50) - (Math.Truncate(listScrollViewer.VerticalOffset / 50));
        if (avanzo == 0)
        {
            numberOfDates = 42;
        }
        else
        {
            numberOfDates = 49;
        }
        int nDaysMonthFirstDate = FindNumberOfDays(ViewModel.listDateDay[FirstDay].Month);
        int firstDayPresent = ViewModel.listDateDay[FirstDay].Day;
        int nDaysPresentFirstDate = nDaysMonthFirstDate - (firstDayPresent - 1);
        int nDaysPresentSecondDate = numberOfDates - (nDaysPresentFirstDate);
        if (nDaysPresentFirstDate > nDaysPresentSecondDate)
        {
            DateTime IncreasedDate = ViewModel.listDateDay[FirstDay].AddMonths(-1);
            indexSucceedingMonth = new DateTime(IncreasedDate.Year, IncreasedDate.Month, 1, 0, 0, 0);
            while (firstdayfound == false)
            {
                if (ViewModel.listDateDay[FirstDay] == indexSucceedingMonth)
                {
                    firstdayfound = true;
                }
                FirstDay -= 1;
            }
        }
        else
        {
            DateTime IncreasedDate = ViewModel.listDateDay[FirstDay];
            indexSucceedingMonth = new DateTime(IncreasedDate.Year, IncreasedDate.Month, 1, 0, 0, 0);
            while (firstdayfound == false)
            {
                if (ViewModel.listDateDay[FirstDay] == indexSucceedingMonth)
                {
                    firstdayfound = true;
                }
                FirstDay -= 1;
            }
        }

        var item = GridDays.ContainerFromIndex(FirstDay + 1) as GridViewItem;
        var gt = item.TransformToVisual(CanvasRootDays);
        double offestCanvas = gt.TransformPoint(new Point(0, 0)).Y;
        listScrollViewer.ChangeView(null, listScrollViewer.VerticalOffset - (offestCanvas * -1), null, false);

        await Task.Delay(300);
    }

    private void btnDecYearMonthDay_Click(object sender, RoutedEventArgs e)
    {

          DecreaseMonth();
    }

    private void btnIncYearMonthDay_Click(object sender, RoutedEventArgs e)
    {

         IncreasesMonth();

    }

    private void btnToday_Click(object sender, RoutedEventArgs e)
    {
        GridDays.SelectedItem = GridDays.Items[ViewModel.indexDDay];
        GridDays.ScrollIntoView(GridDays.Items[ViewModel.indexToDay], ScrollIntoViewAlignment.Leading);
    }
}

CalendarPageViewModel.cs:

class CalendarPageViewModel : ViewModelBase
{
    private ObservableCollection<CalendarDay> _itemsDay;
    private ObservableCollection<DateTime> _listaDateDay = new ObservableCollection<DateTime>();
    private ObservableCollection<DateTime> _listaDateMonth = new ObservableCollection<DateTime>();
    private ObservableCollection<DateTime> _listaDateYear = new ObservableCollection<DateTime>();
    public ObservableCollection<CalendarDay> ItemsDay
    {
        get
        {
            return _itemsDay;
        }
        set
        {
            _itemsDay = value;
            OnPropertyChanged();
        }
    }
    public ObservableCollection<DateTime> listDateDay
    {
        get
        {
            return _listaDateDay;
        }
        set
        {
            _listaDateDay = value;
            OnPropertyChanged();
        }
    }
    public ObservableCollection<DateTime> listDateMonth
    {
        get
        {
            return _listaDateMonth;
        }
        set
        {
            _listaDateMonth = value;
            OnPropertyChanged();
        }
    }
    public ObservableCollection<DateTime> listDateYear
    {
        get
        {
            return _listaDateYear;
        }
        set
        {
            _listaDateYear = value;
            OnPropertyChanged();
        }
    }
    public int indexToDay { get; set; }
    public int indexDDay { get; set; }
    public CalendarPageViewModel()
    {
        var listDay = new ObservableCollection<CalendarDay>();
        DateTime monthToDay = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1, 0, 0, 0);
        DateTime monthToDayM = new DateTime(DateTime.Now.Year, 1, 1, 0, 0, 0);
        DateTime yearsToDay = new DateTime(DateTime.Now.Year, 1, 1, 0, 0, 0);
        DateTime dayOfToday = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0);
        DateTime progressiveDay = new DateTime(1900, 1, 1);
        listDateDay.Add(progressiveDay);
        listDateMonth.Add(new DateTime(progressiveDay.Year, 1, 1, 0, 0, 0));
        listDateYear.Add(new DateTime(progressiveDay.Year, 1, 1, 0, 0, 0));
        for (var i = 0; i < 72000; i++)
        {
            listDay.Add(new CalendarDay { ProgressiveDay = progressiveDay.Day.ToString() });

            progressiveDay = progressiveDay.AddDays(1);
            listDateDay.Add(progressiveDay);
        }
        for (int a = 0; a <= listDateDay.Count - 1; a++)
        {
            if (listDateDay[a] == monthToDay)
            {
                indexToDay = a;
            }
            else if (listDateDay[a] == dayOfToday)
            {
                indexDDay = a;
            }
        }
        _itemsDay = listDay;
    }
}
\$\endgroup\$
2
\$\begingroup\$

Efficiency

The most obvious problem is that you're adding 72.000 CalendarDay items to a collection that your CalendarPicker is binding to:

  1. It's inconvenient. The CalendarPicker can apparently only be used when it's supplied with CalendarDay items. I expect such a control to be usable immediately, while perhaps providing some customization via dependency properties like MinimumDate and MaximumDate.
  2. It's going to be slow if you don't use UI virtualization.
  3. It's inefficient: each CalendarDay contains two brushes and some text. Those brushes can be reused (you only need 2, not 72.000 * 2) and that text can be generated on demand.

Other observations

  • Your control is intricately linked to CalendarPageViewModel. It looks like it's going to be difficult to use in any other context. Custom controls normally don't know anything about view models. Instead, they expose information via dependency properties, which can be used with data binding. In this case, a SelectedDay property would seem appropriate.
  • You only need observable collections when you actually expect items to be added or removed. Likewise, you only need property change notifications when you expect property values to change. None of that seems to apply to CalendarDay.
  • The code contains several 'magic numbers' and hard-coded day/month names. Magic numbers make code more difficult to understand, and hard-coded names make it difficult to localize (or otherwise customize the display of) this control.
  • Instead of a large if/else if chain, consider using a lookup table for FindNumberOfDays - an array will do.

The code in general strikes me as very tightly coupled, and thus difficult to reuse or customize. There's probably more that can be said about this, but I don't have a lot of time right now, so maybe later.

\$\endgroup\$
  • \$\begingroup\$ How can I use UI virtualization? \$\endgroup\$ – LightGreen Sep 27 '17 at 18:35
  • \$\begingroup\$ If you search for 'uwp UI virtualization' you'll find various articles, some on docs.microsoft.com, that describe what it is and how you can use it. It looks like UWP's GridView already supports it automatically. However, your grid view is wrapped in a canvas, which doesn't restrict the size of its children, so your grid view probably won't virtualize anything because it thinks that all its content is visible. \$\endgroup\$ – Pieter Witvoet Sep 28 '17 at 8:26
1
\$\begingroup\$

First of all, you have some duplications both in XAML and code. For example, these lines

<TextBlock Grid.Column="0" Text="lu" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" FontSize="25"/>
<TextBlock Grid.Column="1" Text="ma" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" FontSize="25"/>
<TextBlock Grid.Column="2" Text="me" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" FontSize="25"/>
...

should be rewritten with use of style. You can define either explicit style (with specified x:Key) or implicit one (that will be applied automatically to all controls of the specified type):

<Style TargetType="{x:Type TextBlock}"
       BasedOn="{StaticResource {x:Key TextBlock}}">
    <Setter Property="Foreground"
            Value="White"/>
    <Setter Property="VerticalAlignment"
            Value="Center"/>
    ...
</Style>

Then you can write just:

<TextBlock Grid.Column="0"
           Text="lu"/>

Also please place each property of a control on separate line in XAML because it is very difficult to read the code where lines go far to the right.


I recommend you always use x:Type instead of type-as-string. There are cases where things like local:CalendarDay just will not work. One example is implicit data templates.


You must rewrite the FindStringWeek, FindStringMonth and FindNumberOfDays methods. By the way, if (true == (gs == 8)) looks pretty awkward since you can write just if (gs == 8).

private static readonly Dictionary<int, string> _daysOfWeek = new Dictionary<int, string>
{
    [0] => "monday",
    [1] => "tuesday",
    ...
};

private string FindStringWeek(int gs)
{
    return _daysOfWeek .TryGetValue(gs, out var dayOfWeek)
        ? dayOfWeek
        : string.Empty;
}

You have a lot of manipulations with view-model in code behind. It is not good. Try to move all your business logic to the view-model.


IncreasesMonth method has async void signature. You should avoid such methods and use async Task instead. It is acceptable to mark event handlers with async. IncreasesMonth is used only in btnIncYearMonthDay_Click event handler, so do the following changes:

private async void btnIncYearMonthDay_Click(object sender, RoutedEventArgs e)
{
    await IncreasesMonth();
}

private async Task IncreasesMonth()
{
    ...
}

The same is applied to the DecreaseMonth method.


Are you sure you need setters for observable collection properties in the CalendarPageViewModel? I believe no. So remove setters from the ItemsDay, listDateDay, listDateMonth and listDateYear properties. Starting with C# 6 you even don't need private fields:

public ObservableCollection<CalendarDay> ItemsDay { get; } = new ObservableCollection<CalendarDay>();

Also all properties in C# should have PascalCased names according to naming guidelines. So the listDateDay is a bad name and you should use ListDateDay as well as IndexToDay and IndexDDay.


View-model should never think about view if you want to write good WPF apps which means you should use MVVM correctly. Your indexToDay and indexDDay properties violate this rule. What are these indices? Where are they used in the view model? As I can see you use them only for updating view:

private void btnToday_Click(object sender, RoutedEventArgs e)
{
    GridDays.SelectedItem = GridDays.Items[ViewModel.indexDDay];
    GridDays.ScrollIntoView(GridDays.Items[ViewModel.indexToDay], ScrollIntoViewAlignment.Leading);
}

Accessing Items of an ItemsControl almost always a sign of bad code. You should attach a command to your button which will execute some actions in view model. Those actions will update some properties which will raise PropertyChanged event. It will cause controls in the view that are bound via bindings to those properties get updated. It is the correct workflow in a WPF application so you should definitely refactor your code.

The first thought that come to your mind where you need perform an action on button click should be using of Command.

Also you use view related classes inside a view model:

ViewModel.ItemsDay[a].daysColor = new SolidColorBrush(Color.FromArgb(255, 160, 160, 160));

It is completely wrong. You should expose a property which defines kind of day state and then react on changes of the state in view via binding and converter or triggers.

So you need to read again about MVVM and take a look at examples of proper implementation of it.

\$\endgroup\$
0
\$\begingroup\$

First I would recommend you using debugger tools or code some stopwatches in to see exactly what is causing the slow down. As it seems your question just assumes it's the for loop. You'll want to make sure before trying to optimize that part.

If you need to optimize that part you really have two choices.

One don't load all that data right away. Just load what you need to show and then when clicking forward to next month or year then load up again just enough data to show that if it's not already loaded. You can even tie into the scrollview to know when you need to load more data You can use something like the ConcurrentDictionary GetOrAdd method and have the key be the month/year if you want to load by month/year and then load just that bucket.

Another option if you really want to load the data upfront is don't have the system wait for the data to be loaded but show the screen right away and update the data as it becomes available. This will mean you need to switch the loading onto another thread and have it report back the update on the Dispatcher. You can use Rx for this (example) or code it yourself.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.