我有一个管理车辆和员工的系统,当您根据日期单击他们的名字时,您应该能够看到他们当天可用的时间。
它只会根据之前表格中选择的日期显示 1 天!所以我需要 1 列,但时间可能是 12:30-14:15 等
像这样的视觉效果:
视觉时间视觉时间
图片:
我已经研究过创建自定义控件或用户控件,但我对这个主题的了解很少,我花了几个小时在一个圈子里跑来跑去。
我有一个管理车辆和员工的系统,当您根据日期单击他们的名字时,您应该能够看到他们当天可用的时间。
它只会根据之前表格中选择的日期显示 1 天!所以我需要 1 列,但时间可能是 12:30-14:15 等
像这样的视觉效果:
视觉时间视觉时间
图片:
我已经研究过创建自定义控件或用户控件,但我对这个主题的了解很少,我花了几个小时在一个圈子里跑来跑去。
发布此答案是因为 OP 要求它:
这就是您在 WPF 中执行此操作的方式:
<Window x:Class="MiscSamples.TimeBookings"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MiscSamples"
Title="TimeBookings" Height="300" Width="300">
<Window.Resources>
<local:TimeRangeToVerticalMarginConverter x:Key="VerticalMarginConverter"/>
<local:TimeRangeHeightConverter x:Key="HeightConverter"/>
</Window.Resources>
<ScrollViewer>
<Grid>
<ItemsControl ItemsSource="{Binding Available}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1" Height="60">
<TextBlock Text="{Binding StringFormat='hh tt'}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ItemsControl ItemsSource="{Binding Bookings}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Margin" Value="{Binding Converter={StaticResource VerticalMarginConverter}}"/>
<Setter Property="Height" Value="{Binding Converter={StaticResource HeightConverter}}"/>
<Setter Property="VerticalAlignment" Value="Top"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="#601050FF" BorderBrush="LightSkyBlue" BorderThickness="1"
x:Name="Border">
<Viewbox Stretch="Uniform">
<TextBlock Text="Booked" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="16">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-45"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Viewbox>
<Border.ToolTip>
<ToolTip>
<StackPanel>
<TextBlock>
<Run Text="From" FontWeight="Bold"/>
<Run Text="{Binding StartString, Mode=OneWay}"/>
</TextBlock>
<TextBlock>
<Run Text="To" FontWeight="Bold"/>
<Run Text="{Binding EndString,Mode=OneWay}"/>
</TextBlock>
</StackPanel>
</ToolTip>
</Border.ToolTip>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</ScrollViewer>
代码背后:
public partial class TimeBookings : Window
{
public TimeBookings()
{
InitializeComponent();
DataContext = new TimeBookingsViewModel();
}
}
视图模型:
public class TimeBookingsViewModel
{
public ObservableCollection<DateTime> Available { get; set; }
public ObservableCollection<TimeRange> Bookings { get; set; }
public TimeBookingsViewModel()
{
Available = new ObservableCollection<DateTime>(Enumerable.Range(8, 11).Select(x => new DateTime(2013, 1, 1).AddHours(x)));
Bookings = new ObservableCollection<TimeRange>();
Bookings.Add(new TimeRange(8, 0, 9, 50) {Base = TimeSpan.FromHours(8)});
Bookings.Add(new TimeRange(10, 0, 11, 00) { Base = TimeSpan.FromHours(8) });
Bookings.Add(new TimeRange(12, 00, 13, 30) { Base = TimeSpan.FromHours(8) });
}
}
数据项:
public class TimeRange
{
public TimeSpan Base { get; set; }
public TimeSpan Start { get; set; }
public TimeSpan End { get; set; }
public string StartString { get { return new DateTime(Start.Ticks).ToString("hh:mm tt"); } }
public string EndString { get { return new DateTime(End.Ticks).ToString("hh:mm tt"); } }
public TimeRange(int starthour, int startminute, int endhour, int endminute)
{
Start = new TimeSpan(0, starthour, startminute, 0);
End = new TimeSpan(0, endhour, endminute, 0);
}
}
还有一些帮手(转换器等):
public class TimeRangeToVerticalMarginConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is TimeRange))
return null;
var range = (TimeRange) value;
return new Thickness(2, range.Start.TotalMinutes - range.Base.TotalMinutes, 2, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class TimeRangeHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is TimeRange))
return null;
var range = value as TimeRange;
return range.End.Subtract(range.Start).TotalMinutes;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Viewbox
,使其拉伸到可用大小。您可以根据需要更改它,但我想不出更好的方法来使文本适合不同预订的可用空间。ToolTip
。你真的可以在 WPF 中做你想做的事。底线:
忘记winforms吧,它太有限了,它没有(真正的)数据绑定,它需要很多代码来做更少的事情,它不支持任何级别的自定义,它迫使你创建像UI一样糟糕的Windows 95。
WPF Rocks:只需将我的代码复制并粘贴到 a 中File -> New Project -> WPF Application
,然后自己查看结果。
评估您将花费在开发控件上的时间,将其乘以您的成本/小时,添加一些您将(肯定)产生的错误,并将其与一些现有的、经过良好测试的解决方案进行比较:
http://www.telerik.com/products/winforms/scheduler.aspx
http://www.devexpress.com/Products/NET/Controls/WinForms/Scheduler/
我建议你购买你的控制(或一些)。
您最好的选择是创建一个从类似控件继承的自定义控件,对于您的示例,类似于图片框的内容可能是有益的。有关自定义控件的(稍微过时的 C++)演练,请参阅:http: //msdn.microsoft.com/en-us/library/ms364048 (v=vs.80).aspx
至于自定义绘图设置:http: //msdn.microsoft.com/en-us/library/windows/desktop/bb761817 (v=vs.85).aspx
作为概括,想法如下:捕获 WM_PAINT 事件,并在该事件中将预先绘制的图像渲染到控件(这通常通过创建绘制表面来完成,然后将其复制到可渲染的痛苦中 -控制区域)这种方法避免了任何“闪烁”。
绘图命令大多很简单,'drawline(xy_start, xy_end)。
最后,为了处理一天中的时间,如果您采用 (rendersurface.height / (24*60)) 您将有一个从时间到像素的转换。例如:
double convert_Size = (rendersurface.height / (24*60)); //height / Hours_in_day * Minites
int time = (hour * 60) + minite_past_hour;
Pixels_from_top = time * convert_Size;
Pixels_from_top 现在是白天该时间的像素 y 坐标。