Проект "Отпуск сотрудников". Часть 3, интерфейс пользователя.
!!НАШ БЛОГ ПЕРЕЕХАЛ!!
Мы создали свой сайт! Все материалы, опубликованные в этом блоге, переехали туда.
Наш новый сайт maddevelop.ru
Клиентскую часть реализуем с помощью технологии WPF, воспользовавшись паттерном MVVM. Модели будут те же, что и в серверной части. Просто скопируем их в созданную папку Models. Создадим класс MainViewModel, в котором опишем взаимодействие с API контроллером и с интерфейсом пользователя. Большинство свойств класса модели представления реализуют метод OnPropertyChanged(), поэтому изменение свойства приводит к изменению элемента управления, к которому оно привязано.
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string prop = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
Окно пользователя будет выглядеть следующим образом:
Цифрами обозначены элементы управления для удобства дальнейшего описания. В таблице ниже удобно посмотреть, как организована привязка свойства класса MainViewModel к свойству элемента управления пользовательского интерфейса.
К свойству Command кнопок следует привязать свойство класса RelayCommand в модели представления.
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
Вторая таблица в пользовательском интерфейсе состоит из четырёх: по одной для каждого квартала года. Для того, чтобы к ним можно было привязывать свойства типа двумерных массивов, следует установить пакет NuGet "Gu.Wpf.DataGrid2D". Эти матрицы составим из объектов типа Cell.
public Cell[,] FirstQuarter { get; set; }
public class Cell
{
public Color Color { get; }
public Cell(Color color)
{
Color = color;
}
}
Выделение отпусков сотрудников цветом возможно, если к фону каждой ячейки привязать через конвертер значений свойство Color класса Cell.
public class ConvMycolorColor : IValueConverter
{
private static System.Drawing.Color color;
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
color = System.Drawing.Color.FromArgb(((Models.Color)value).ColorNumber);
return new SolidColorBrush(System.Windows.Media.Color
.FromArgb(color.A, color.R, color.G, color.B));
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
Запишем для ясности определение всех полей и свойств класса MainViewModel.
public class MainViewModel : INotifyPropertyChanged
{
private static IEnumerable<Employee> employees;
private static HttpClient client = new HttpClient();
private StringBuilder errorSB = new StringBuilder();
// Привязанное свойство №1 из таблицы выше
private string name;
public string Name
{
get => name;
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
// №2
public IEnumerable<Color> Colors { get; set; }
// Также для элемента управления №2
private Color empColor;
public Color EmpColor
{
get => empColor;
set
{
empColor = value;
OnPropertyChanged(nameof(empColor));
}
}
// №3
public RelayCommand AddEmployee { get; set; }
// №4
public RelayCommand DeleteEmployee { get; set; }
// №8
public RelayCommand CommandAddVacation { get; set; }
// №9
public RelayCommand CommandDeleteVacation { get; set; }
// №15
public RelayCommand CommandRefresh { get; set; }
// №5
private Employee currentEmployee;
public Employee CurrentEmployee
{
get => currentEmployee;
set
{
currentEmployee = value;
OnPropertyChanged(nameof(CurrentEmployee));
}
}
// Привязываемое свойство к свйоству также элемента №5
private Vacation currentVacation;
public Vacation CurrentVacation
{
get => currentVacation;
set
{
currentVacation = value;
OnPropertyChanged(nameof(CurrentVacation));
if (currentVacation != null)
{
Start = currentVacation.Start;
Duration = currentVacation.Duration.ToString();
}
}
}
// №6
private DateTime start = DateTime.Now;
public DateTime Start
{
get => start;
set
{
start = value;
OnPropertyChanged(nameof(Start));
}
}
// №7
private string duration;
public string Duration
{
get => duration;
set
{
duration = value;
OnPropertyChanged(nameof(Duration));
}
}
// №10
private DataView table;
public DataView Table
{
get => table;
set
{
table = value;
OnPropertyChanged(nameof(Table));
EmployeeNames = employees.Select(x => x.Name).ToList();
}
}
// №10
private DataRowView currentRow;
public DataRowView CurrentRow
{
get => currentRow;
set
{
currentRow = value;
OnPropertyChanged(nameof(CurrentRow));
if (currentRow != null)
{
CurrentEmployee = employees
.FirstOrDefault(e => e.Name
== currentRow.Row.ItemArray.ElementAt(0) as string);
Name = CurrentEmployee.Name;
EmpColor = Colors.FirstOrDefault(c => CurrentEmployee.ColorId == c.ColorId);
}
}
}
// №11
private IEnumerable<string> employeeNames;
public IEnumerable<string> EmployeeNames
{
get => employeeNames;
set
{
employeeNames = value;
OnPropertyChanged(nameof(EmployeeNames));
}
}
// №11
public Cell[,] FirstQuarter { get; set; }
// №12
public Cell[,] SecondQuarter { get; set; }
// №13
public Cell[,] ThirdQuarter { get; set; }
// №14
public Cell[,] FourthQuarter { get; set; }
// №16
private string error;
// Свойство для записывания ошибок
public string Error
{
get => error;
set
{
error = value;
OnPropertyChanged(nameof(Error));
}
}
Напоследок приведём XAML-разметку окна программы:
<Window x:Class="FrontendWPF.MainWindow"
xmlns="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:dataGrid2D="http://gu.se/DataGrid2D"
xmlns:local="clr-namespace:FrontendWPF"
mc:Ignorable="d"
Title="MainWindow" SizeToContent="WidthAndHeight">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<local:ConvMyColorString x:Key="myColorConverter"/>
<local:ConvMycolorColor x:Key="colorToTrueColor"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="85"/>
<ColumnDefinition Width="25"/>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="85"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Отпуски" Grid.Column="4" Margin="5"
HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
<ComboBox Grid.Column="4" Grid.Row="1" Margin="5" Width="80"
HorizontalAlignment="Left" SelectedItem="{Binding CurrentVacation}"
ItemStringFormat="d" ItemsSource="{Binding CurrentEmployee.Vacations}"
DisplayMemberPath="Start"/>
<TextBlock Text="Дата начала:" Margin="5" Grid.Column="5"
HorizontalAlignment="Right"/>
<TextBlock Text="Длительность:" Margin="5" Grid.Column="5" Grid.Row="1"
HorizontalAlignment="Right"/>
<DatePicker Grid.Column="6" Margin="5" SelectedDateFormat="Short"
SelectedDate="{Binding Start}" DisplayDateStart="2019/01/01"
DisplayDateEnd="2019/12/31"/>
<TextBox Grid.Column="6" Grid.Row="1" Margin="5" Width="22"
HorizontalAlignment="Left"
Text="{Binding Duration, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="Добавить" Grid.Column="7" Width="75" Margin="5"
Command="{Binding CommandAddVacation}"/>
<Button Content="Удалить" Grid.Column="7" Grid.Row="1" Width="75" Margin="5"
Command="{Binding CommandDeleteVacation}"/>
<TextBlock Text="ФИО:" Margin="5" HorizontalAlignment="Right"/>
<TextBox Grid.Column="1" Margin="5"
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="Цвет:" Grid.Row="1" Margin="5" HorizontalAlignment="Right"/>
<ComboBox Grid.Column="1" Grid.Row="1" Margin="5" Width="80"
HorizontalAlignment="Left" SelectedItem="{Binding EmpColor}"
ItemsSource="{Binding Colors}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource
myColorConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Добавить" Grid.Column="2" Width="75" Margin="5"
Command="{Binding AddEmployee}"/>
<Button Content="Удалить" Grid.Column="2" Grid.Row="1" Width="75" Margin="5"
Command="{Binding DeleteEmployee}"/>
</Grid>
</Grid>
<DataGrid ItemsSource="{Binding Table}" CanUserAddRows="False"
HorizontalAlignment="Left" Grid.Row="1" SelectedItem="{Binding CurrentRow}"/>
<TextBlock Grid.Row="2" Text="График отпусков на 2019 год" FontWeight="Bold"
Margin="5,20,5,5"/>
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="5">
<StackPanel>
<TextBlock Text="1 квартал" HorizontalAlignment="Center"/>
<DataGrid SelectionUnit="Cell" ColumnWidth="3"
dataGrid2D:ItemsSource.Array2D="{Binding FirstQuarter,
UpdateSourceTrigger=PropertyChanged}" HeadersVisibility="Row"
MinColumnWidth="2" GridLinesVisibility="Horizontal"
HorizontalAlignment="Left" dataGrid2D:ItemsSource.RowHeadersSource=
"{Binding EmployeeNames, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.Resources>
<dataGrid2D:Cell.Template>
<DataTemplate>
<Grid Background="{Binding Color, Converter={StaticResource
colorToTrueColor}}" Width="3"/>
</DataTemplate>
</dataGrid2D:Cell.Template>
</DataGrid>
</StackPanel>
<StackPanel>
<TextBlock Text="2 квартал" HorizontalAlignment="Center"/>
<DataGrid SelectionUnit="Cell" dataGrid2D:ItemsSource.Array2D=
"{Binding SecondQuarter, UpdateSourceTrigger=PropertyChanged}"
ColumnWidth="3" HeadersVisibility="None" MinColumnWidth="2"
GridLinesVisibility="Horizontal" HorizontalAlignment="Left"
RowHeight="22">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.Resources>
<dataGrid2D:Cell.Template>
<DataTemplate>
<Grid Background="{Binding Color, Converter={StaticResource
colorToTrueColor}}" Width="3"/>
</DataTemplate>
</dataGrid2D:Cell.Template>
</DataGrid>
</StackPanel>
<StackPanel>
<TextBlock Text="3 квартал" HorizontalAlignment="Center"/>
<DataGrid SelectionUnit="Cell" dataGrid2D:ItemsSource.Array2D="{Binding
ThirdQuarter, UpdateSourceTrigger=PropertyChanged}" ColumnWidth="3"
HeadersVisibility="None" MinColumnWidth="2" RowHeight="22"
GridLinesVisibility="Horizontal" РorizontalAlignment="Left">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.Resources>
<dataGrid2D:Cell.Template>
<DataTemplate>
<Grid Background="{Binding Color, Converter={StaticResource
colorToTrueColor}}" Width="3"/>
</DataTemplate>
</dataGrid2D:Cell.Template>
</DataGrid>
</StackPanel>
<StackPanel>
<TextBlock Text="4 квартал" HorizontalAlignment="Center"/>
<DataGrid SelectionUnit="Cell" dataGrid2D:ItemsSource.Array2D="{Binding
FourthQuarter, UpdateSourceTrigger=PropertyChanged}" ColumnWidth="3"
HeadersVisibility="None" MinColumnWidth="2" RowHeight="22"
GridLinesVisibility="Horizontal" HorizontalAlignment="Left">
<DataGrid.Resources>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0"/>
</Style>
</DataGrid.Resources>
<dataGrid2D:Cell.Template>
<DataTemplate>
<Grid Background="{Binding Color, Converter={StaticResource
colorToTrueColor}}" Width="3"/>
</DataTemplate>
</dataGrid2D:Cell.Template>
</DataGrid>
</StackPanel>
</StackPanel>
<StackPanel Grid.Row="4" Orientation="Horizontal">
<Button Content="Обновить" Width="80" Command="{Binding CommandRefresh}"
HorizontalAlignment="Right" Margin="5,0,20,5"
VerticalAlignment="Center"/>
<TextBlock Text="Ошибки:" Margin="5,0,0,5" VerticalAlignment="Center"/>
<TextBox Width="300" Margin="5,0,5,5" Text="{Binding Error}"/>
</StackPanel>
</Grid>
</Window>
В следующей части рассмотрим, каким образом происходит заполнение двух таблиц в интерфейсе пользователя.
Ещё больше интересной информации на нашем Telegram-канале.
<< К части 2 << .........