Actualmente estoy aprendiendo XAML / C # y escribiendo una aplicación de calendario. En este momento está creando una cuadrícula y luego aplicando elementos de control de usuario a la cuadrícula. Está construyendo correctamente mi calendario pero, en lugar de definir el número de filas en XAML, quiero poder establecer el número a través de C # dinámicamente. Algunos meses usan más o menos semanas (marzo necesita 5 pero abril necesita 6). Me pregunto cómo hacer eso o si debería usar un control diferente al de la cuadrícula.
Así es como se ve la interfaz de usuario.
Código 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ódigo 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();
}
}
}
Sé que realmente debería usar MVVM, pero todavía estoy envolviendo mi cerebro con la programación en el patrón MVVM y quiero que esto funcione. Probablemente lo refactorice una vez que me sienta más cómodo con él.
Solo quería terminar este proyecto
Comprendido. La cuestión es que la idea básica detrás de MVVM no es realmente tan difícil, y si la acepta, es probable que termine el proyecto más rápido que si continúa intentando codificar toda su interfaz de usuario. No puedo garantizar eso, por supuesto. Pero yo he pasado por lo mismo y puedo decirte que puedes pasar mucho tiempo luchando contra WPF tratando de configurar la interfaz de usuario en código subyacente explícitamente.
Sin un buen ejemplo de código mínimo, completo y verificable para empezar, no me resultó práctico replicar exactamente su interfaz de usuario. Pero aquí hay un ejemplo de código simple que muestra el enfoque básico que puede tomar para usar MVVM para crear la interfaz de usuario que desea ...
Primero, es útil tener una clase base que se implemente INotifyPropertyChanged
por usted. Simplifica mucho el texto estándar del modelo de vista:
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));
}
}
Entonces, necesitaremos ver modelos. En esta interfaz de usuario, hay dos componentes básicos: la vista general y los días individuales del mes. Entonces, hice un modelo de vista para cada uno:
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); }
}
}
y…
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 });
}
}
}
Como puede ver, hasta ahora no se ha mencionado la interfaz de usuario y, sin embargo, ya existe toda la lógica para construir las fechas correspondientes a un mes. La parte de la interfaz de usuario, el XAML, no tendrá idea de que está haciendo algo relacionado con meses o fechas. Lo más cercano que se obtiene es un invariante codificado, es decir, el número de días de una semana que se utilizan para controlar el número de columnas en el UniformGrid
que se mostrarán sus datos.
El XAML tiene este aspecto:
<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>
El XAML hace tres cosas:
MonthViewViewModel
objeto que se utilizará como DataContext
para el Window
. Un objeto en el árbol visual, es decir, los hijos de Window
, heredará el contexto de su padre si no tiene ninguno propio.{Binding...}
sintaxis. En muchos casos (por ejemplo, texto, números, valores de enumeración), puede enlazar directamente y la conversión predeterminada hará lo que desee (como es el caso anterior). De lo contrario, puede implementar el suyo IValueConverter
e incorporarlo en el enlace.MonthViewViewModel
que se muestre, al declarar a ContentControl
, donde el contenido de ese control está vinculado simplemente al contexto de datos actual (una {Binding}
expresión sin una ruta se vinculará a la fuente, y la fuente predeterminada es el contexto de datos actual) .En el contexto del ContentControl
, así como de los elementos individuales que se muestran en el ItemsControl
, WPF buscará la plantilla que sea apropiada para el objeto de datos definido para ese contexto, y automáticamente poblará su árbol visual, vinculando las propiedades necesarias, según a ese objeto.
Hay una serie de ventajas en este enfoque, las principales son que puede describir su interfaz de usuario en lugar de tener que codificarla , y que mantiene el principio de POO de "separación de preocupaciones", que es clave para reducir el trabajo mental. carga involucrada al permitirle concentrarse en una cosa a la vez, en lugar de tener que lidiar con la interfaz de usuario y la lógica de datos juntos.
Un par de notas al margen sobre el XAML anterior:
p:
espacio de nombres XML y lo usé para el Style
elemento. Esto solo es una solución a un error de desbordamiento de pila, en el que el Style
elemento por sí mismo confunde al formateador XML y evita que el elemento y sus elementos secundarios se formatee correctamente. El XAML se compilará bien así, pero en realidad no es necesario en código real. En su XAML habitual, puede omitirlo de forma segura.<TextBlock Text="{Binding DayNumber}" Background="LightBlue">
, esa sintaxis tendrá prioridad sobre cualquier <Setter...>
elemento en un estilo. Debe recordar establecer el valor predeterminado de cualquier propiedad que desee establecer a través de un disparador, en su propio <Setter...>
estilo también (como se muestra arriba).Este artículo se recopila de Internet, indique la fuente cuando se vuelva a imprimir.
En caso de infracción, por favor [email protected] Eliminar
Déjame decir algunas palabras