我目前正在自学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模式编程并想要让它工作。一旦我对它感到满意,我可能会重构它。
答案 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做了三件事:
MonthViewViewModel
对象用作DataContext
的{{1}}。视觉树中的一个对象,即Window
的子对象,如果没有自己的父对象的上下文,则会继承它们。Window
语法在模板中引用。在许多情况下(例如文本,数字,枚举值),您可以直接绑定,默认转换将执行您想要的操作(如上所述)。如果没有,您可以实现自己的{Binding...}
并将其合并到绑定中。IValueConverter
为MonthViewViewModel
提供了显示位置,其中该控件的内容仅与当前数据上下文绑定(ContentControl
表达式路径将绑定到源,默认源是当前数据上下文。在{Binding}
的上下文中,以及ContentControl
中显示的各个项目,WPF将搜索适合为该上下文定义的数据对象的模板,并且根据该对象自动填充可视树,绑定到必要的属性。
这种方法有许多优点,主要是你可以描述你的用户界面,而不必代码它,并且你维护OOP &#34;关注点分离的原则,这是减少所涉及的心理工作负荷的关键,允许你一次只关注一件事,而不是必须一起处理UI和数据逻辑。
关于上述XAML的几个附注:
ItemsControl
XML命名空间并将其用于p:
元素。这只是 解决Stack Overflow错误,其中Style
元素本身会混淆XML格式化程序并阻止元素及其子元素被正确格式化。 XAML会像这样编译得很好,但在实际代码中它实际上并不是必需的。在常规XAML中,您可以安全地省略它。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);
}
}