Wpf UserControl построить Timeline
Хочу сделать UserControl который отображал бы список задач у пользователя по времени, и поддерживал DragAndDrop для внесения задач.
Примерно как-то так, только без табличного представления:
Timeline отображает период от 00:00 до 23:59.
Только вместо Group должно быть имя пользователя.
Объект пользователя:
public class UserViewModel : Screen
{
private BindableCollection<Task> _userTask;
private readonly Employee _employer;
public UserViewModel(Employee employer)
{
this._employer = employer;
UserTask = new BindableCollection<Task>();
}
public long Id => _employer.Id;
public string Fullname => $"{_employer.Surname} {_employer.Name} {_employer.Patronymic}";
public BindableCollection<Task> UserTask
{
get => _userTask;
set => Set(ref _userTask, value, nameof(UserTask));
}
}
Объект задачи:
public class Task
{
public long Id { get; set; }
public TaskType TaskType { get; set; }
public string Description { get; set; }
public Location Location { get; set; }
public DateTime StartDatetime { get; set; }
public DateTime EndDatetime { get; set; }
}
На текущий момент имею следующий вид:
При перетаскивании на пользователя, они привязываются к нему.
Как сделать такого вида TimeLine?
Update
Добавил разметку предложенную @aeport
<!-- Отображение сотрудников v2-->
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Users}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid SnapsToDevicePixels="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Background="LightGray">
<TextBlock Padding="10,5" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Fullname}" TextWrapping="WrapWithOverflow"/>
</Border>
<Border Grid.ColumnSpan="2" BorderThickness="0,0,0,1">
<Border.BorderBrush>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle StrokeDashArray="4 4"
Stroke="Black"
StrokeThickness="1"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"/>
</VisualBrush.Visual>
</VisualBrush>
</Border.BorderBrush>
</Border>
<ItemsControl Grid.Column="1" ItemsSource="{Binding UserTask}"
draganddrop:DragDropHelper.IsDragSource="True"
draganddrop:DragDropHelper.IsDropTarget="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Fill="Green" Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Canvas}}" HorizontalAlignment="Left">
<Rectangle.Width>
<MultiBinding Converter="{StaticResource MultiDateTimeToWidthConverter}">
<Binding Path="StartDatetime"/>
<Binding Path="EndDatetime"/>
</MultiBinding>
</Rectangle.Width>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="AliceBlue" Margin="0,0,0,1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding StartDatetime, Converter={StaticResource StartDateTimeToCanvasLeftPositionConverter}}" />
<Setter Property="Canvas.Top" Value="0" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
Расчет стартовой точки
public class StartDateTimeToCanvasLeftPositionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double startTime = (value != null && value != DependencyProperty.UnsetValue) ? ((DateTime) value).TimeOfDay.TotalSeconds : 0d;
return 86400/startTime;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Расчёт ширины элемента
public class MultiDateTimeToWidthConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var startTime = (values[0] != null && values[0] != DependencyProperty.UnsetValue) ? ((DateTime) values[0]).TimeOfDay.TotalSeconds : 0d;
var endTime = (values[1] != null && values[1] != DependencyProperty.UnsetValue) ? ((DateTime) values[1]).TimeOfDay.TotalSeconds : 0d;
double timeDifference = endTime - startTime;
return timeDifference <= 1d ? 1d : (object)(86400 / timeDifference);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Выглядит вот так:
Как теперь расчитать стартовую точку от DateTime и ширину элемента от 2 dateTime.



