使用C#

时间:2017-03-31 18:42:13

标签: c# wpf xaml

我目前正在自学XAML / C#并编写日历应用程序。现在它创建了一个网格,然后将用户控制元素应用到网格中。它正在构建我的日历,但是,我希望能够动态地通过C#设置数字,而不是在XAML中定义行数。几个月使用或多或少的周(3月需要5,但4月需要6)。我想知道如何做到这一点,或者我是否应该使用与网格不同的控件。

This is what the UI looks like.

XAML代码

<UserControl x:Class="CMS.Control.MonthView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">

    <Grid VerticalAlignment="Stretch">
        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Background="AliceBlue">
            <Image x:Name="MonthPrev" Source="/Images/Previous.png" Height="24" Margin="16,0,6,0" MouseLeftButtonUp="MonthPrev_MouseLeftButtonUp"/>
            <Image x:Name="MonthNext" Source="/Images/Next.png" Height="24" Margin="6,0,16,0" MouseLeftButtonUp="MonthNext_MouseLeftButtonUp"/>
            <Label x:Name="DateLabel" Content="January 2017" FontSize="16" FontFamily="Bold" VerticalAlignment="Center"/>
        </StackPanel>
        <Grid Grid.Row="1" Background="AliceBlue">
            <Grid.ColumnDefinitions>

                <ColumnDefinition MinWidth="60" Width="*"/>
                <ColumnDefinition MinWidth="60" Width="*"/>
                <ColumnDefinition MinWidth="60" Width="*"/>
                <ColumnDefinition MinWidth="60" Width="*"/>
                <ColumnDefinition MinWidth="60" Width="*"/>
                <ColumnDefinition MinWidth="60" Width="*"/>
                <ColumnDefinition MinWidth="60" Width="*"/>
            </Grid.ColumnDefinitions>
            <Label Grid.Column="0" Content="Sunday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
            <Label Grid.Column="1" Content="Monday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
            <Label Grid.Column="2" Content="Tuesday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
            <Label Grid.Column="3" Content="Wednesday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
            <Label Grid.Column="4" Content="Thursday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
            <Label Grid.Column="5" Content="Friday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
            <Label Grid.Column="6" Content="Saturday" FontSize="9" Margin="2,0,0,2" Padding="0,1,0,0" HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0,0,1,0"/>
        </Grid>

        <Grid x:Name="WeekRowGrid" Grid.Row="2">
            <Grid.ColumnDefinitions>

                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>

            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
        </Grid>
    </Grid>
</UserControl>

C#代码

namespace CMS.Control
{
    /// <summary>
    /// Interaction logic for MonthView.xaml
    /// </summary>
    public partial class MonthView : UserControl
    {
        private DateTime _DispayDate;

        public MonthView()
        {
            InitializeComponent();
            _DispayDate = DateTime.Now;
            DrawMonth();
        }

        //Generates the 
        private void DrawMonth()
        {
            DateTime FirstDayOfMonth = new DateTime(_DispayDate.Year, _DispayDate.Month, 1);
            int DisplayFrontPadding = (int)FirstDayOfMonth.DayOfWeek; // # of days that need to be displayed before the 1st of the month
            int DaysInDisplayMonth = DateTime.DaysInMonth(_DispayDate.Year, _DispayDate.Month);
            int DaysInDisplay = DisplayFrontPadding + DaysInDisplayMonth; 
            DaysInDisplay += 7 - DaysInDisplay%7; // Rounds up the displayed days to a multiple of 7

            DateLabel.Content = _DispayDate.ToString("MMMM") + " " + _DispayDate.Year;

            for (int i = 0; i<DaysInDisplay; i++)
            {
                DateTime DisplayDay = FirstDayOfMonth.AddDays(i - DisplayFrontPadding);
                DayBox DB = DayBox.GetDay();   // DayBox factory
                DB.DrawDay(DisplayDay);

                Grid.SetRow(DB, i / 7);
                WeekRowGrid.Children.Add(DB);
                Grid.SetColumn(DB, i%7);
            }
        }

        //Generates a calendar for the previous month on button click.
        private void MonthPrev_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _DispayDate = _DispayDate.AddMonths(-1);
            DrawMonth();
        }

        //Generates a calendar for the next month on button click.
        private void MonthNext_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            _DispayDate = _DispayDate.AddMonths(1);
            DrawMonth();
        }
    }
}

我知道我应该真正使用MVVM,但我仍然围绕MVVM模式编程并想要让它工作。一旦我对它感到满意,我可能会重构它。

2 个答案:

答案 0 :(得分:1)

  

我只是想完成这个项目

解读。事实上,MVVM背后的基本理念并不是那么难,如果你接受它,你可能会比你继续尝试硬编码更快地完成项目 所有的用户界面。当然,我无法保证。但是我已经完成了同样的事情,我可以告诉你,你可以花很多时间与WPF作战,试图明确地在代码隐藏中配置UI。

如果没有一个好的Minimal, Complete, and Verifiable code example,我完全复制您的用户界面并不实用。但这里有一个简单的代码示例,展示了使用MVVM构建所需UI的基本方法......

首先,拥有一个为您实现INotifyPropertyChanged的基类会很有帮助。它简化了视图模型样板:

class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void _UpdateField<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, newValue))
        {
            field = newValue;
            _OnPropertyChanged(propertyName);
        }
    }

    protected virtual void _OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

然后,我们需要查看模型。在此UI中,有两个基本组件:整体视图和每月的各个日期。所以,我为每个人制作了一个视图模型:

class DateViewModel : NotifyPropertyChangedBase
{
    private int _dayNumber;
    private bool _isCurrent;

    public int DayNumber
    {
        get { return _dayNumber; }
        set { _UpdateField(ref _dayNumber, value); }
    }

    public bool IsCurrent
    {
        get { return _isCurrent; }
        set { _UpdateField(ref _isCurrent, value); }
    }
}

和...

class MonthViewViewModel : NotifyPropertyChangedBase
{
    private readonly ObservableCollection<DateViewModel> _dates = new ObservableCollection<DateViewModel>();

    private DateTime _selectedDate;

    public DateTime SelectedDate
    {
        get { return _selectedDate; }
        set { _UpdateField(ref _selectedDate, value); }
    }

    public IReadOnlyCollection<DateViewModel> Dates
    {
        get { return _dates; }
    }

    protected override void _OnPropertyChanged(string propertyName)
    {
        base._OnPropertyChanged(propertyName);

        switch (propertyName)
        {
            case nameof(SelectedDate):
                _UpdateDates();
                break;
        }
    }

    private void _UpdateDates()
    {
        _dates.Clear();

        DateTime firstDayOfMonth = new DateTime(SelectedDate.Year, SelectedDate.Month, 1),
            firstDayOfNextMonth = firstDayOfMonth.AddMonths(1);
        int previousMonthDates = (int)firstDayOfMonth.DayOfWeek; // assumes Sunday-start week
        int daysInView = previousMonthDates + DateTime.DaysInMonth(SelectedDate.Year, SelectedDate.Month);

        // round up to nearest week multiple
        daysInView = ((daysInView - 1) / 7 + 1) * 7;

        DateTime previousMonth = firstDayOfMonth.AddDays(-previousMonthDates);

        for (DateTime date = previousMonth; date < firstDayOfNextMonth; date = date.AddDays(1))
        {
            _dates.Add(new DateViewModel { DayNumber = date.Day, IsCurrent = date == SelectedDate.Date });
        }

        for (int i = 1; _dates.Count < daysInView; i++)
        {
            _dates.Add(new DateViewModel { DayNumber = i, IsCurrent = false });
        }
    }
}

正如您所看到的,到目前为止还没有提及UI,但是已经存在构建一个月的日期的所有逻辑。 UI部分XAML将不知道您正在做与月或日期相关的任何事情。最接近的是硬编码不变量,即一周中用于控制UniformGrid中将显示数据的列数的天数。

XAML看起来像这样:

<Window x:Class="TestSO43147585CalendarMonthView.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:p="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-compatibility/2006"
        xmlns:l="clr-namespace:TestSO43147585CalendarMonthView"
        xmlns:s="clr-namespace:System;assembly=mscorlib"
        mc:Ignorable="d"
        SizeToContent="Height"
        Title="MainWindow" Height="350" Width="525">
  <Window.DataContext>
    <l:MonthViewViewModel SelectedDate="{x:Static s:DateTime.Today}"/>
  </Window.DataContext>

  <Window.Resources>
    <DataTemplate DataType="{x:Type l:MonthViewViewModel}">
      <ItemsControl ItemsSource="{Binding Dates}">
        <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
            <UniformGrid IsItemsHost="True" Columns="7"/>
          </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
      </ItemsControl>
    </DataTemplate>

    <DataTemplate DataType="{x:Type l:DateViewModel}">
      <Border BorderBrush="Black" BorderThickness="0, 0, 1, 0">
        <StackPanel>
          <TextBlock Text="{Binding DayNumber}">
            <TextBlock.Style>
              <p:Style TargetType="TextBlock">
                <Setter Property="Background" Value="LightBlue"/>
                <p:Style.Triggers>
                  <DataTrigger Binding="{Binding IsCurrent}" Value="True">
                    <Setter Property="Background" Value="Yellow"/>
                  </DataTrigger>
                </p:Style.Triggers>
              </p:Style>
            </TextBlock.Style>
          </TextBlock>
          <Grid Height="{Binding ActualWidth, RelativeSource={x:Static RelativeSource.Self}}"/>
        </StackPanel>
      </Border>
    </DataTemplate>
  </Window.Resources>

  <Grid>
    <ContentControl Content="{Binding}" VerticalAlignment="Top"/>
  </Grid>
</Window>

XAML做了三件事:

  1. 它声明MonthViewViewModel对象用作DataContext的{​​{1}}。视觉树中的一个对象,即Window的子对象,如果没有自己的父对象的上下文,则会继承它们。
  2. 它声明了两个视图模型的数据模板。这些告诉WPF它应该如何直观地表示数据。视图模型包含您要表示的数据,并且该数据通过Window语法在模板中引用。在许多情况下(例如文本,数字,枚举值),您可以直接绑定,默认转换将执行您想要的操作(如上所述)。如果没有,您可以实现自己的{Binding...}并将其合并到绑定中。
  3. 它通过声明IValueConverterMonthViewViewModel提供了显示位置,其中该控件的内容仅与当前数据上下文绑定(ContentControl表达式路径将绑定到源,默认源是当前数据上下文。
  4. {Binding}的上下文中,以及ContentControl中显示的各个项目,WPF将搜索适合为该上下文定义的数据对象的模板,并且根据该对象自动填充可视树,绑定到必要的属性。

    这种方法有许多优点,主要是你可以描述你的用户界面,而不必代码它,并且你维护OOP &#34;关注点分离的原则,这是减少所涉及的心理工作负荷的关键,允许你一次只关注一件事,而不是必须一起处理UI和数据逻辑。

    关于上述XAML的几个附注:

    • 您可能会注意到我添加了ItemsControl XML命名空间并将其用于p:元素。这只是 解决Stack Overflow错误,其中Style元素本身会混淆XML格式化程序并阻止元素及其子元素被正确格式化。 XAML会像这样编译得很好,但在实际代码中它实际上并不是必需的。在常规XAML中,您可以安全地省略它。
    • 我添加了您的代码没有的功能,仅用于说明目的。也就是说,当前日期以黄色突出显示。此处显示的技术非常有用,因为它允许您根据视图模型的属性值自定义单个模板中项目的外观。但是有一个小陷阱:在WPF中,如果您通过属性语法显式设置了元素的属性,例如类似Style之类的东西,那么该语法将优先于样式中的任何<TextBlock Text="{Binding DayNumber}" Background="LightBlue">元素。您必须记住通过触发器设置您打算设置的任何属性的默认值,也可以在样式中设置自己的<Setter...>(如上所示)。

答案 1 :(得分:0)

您可以创建一个方法,根据周数(行数)自动调整行数。此方法始终删除所有行,然后添加所需的正确行数。

private void AdjustRowDefinitions(int numberOfWeeks)
{
    WeekRowGrid.RowDefinitions.Clear();
    for (int i = 0; i < numberOfWeeks; i++)
    {
        RowDefinition rowDef = new RowDefinition();
        rowDef.Height = new GridLength(1, GridUnitType.Star); //this sets the height of the row to *
        WeekRowGrid.RowDefinitions.Add(rowDef);
    }
}