Проект "Отпуск сотрудников". Часть 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 << .........