3

我有一个管理车辆和员工的系统,当您根据日期单击他们的名字时,您应该能够看到他们当天可用的时间。

它只会根据之前表格中选择的日期显示 1 天!所以我需要 1 列,但时间可能是 12:30-14:15 等

像这样的视觉效果:

视觉时间视觉时间

图片:

在此处输入图像描述

我已经研究过创建自定义控件或用户控件,但我对这个主题的了解很少,我花了几个小时在一个圈子里跑来跑去。

4

3 回答 3

4

发布此答案是因为 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();
    }
}
  • UI 通过使用MVVMDataBindingWPF Mentality与数据和逻辑分离
  • 这让你的代码几乎是空的,你的应用程序代码真的很干净,只需要处理你自己的类和属性,而无需理会 UI。
  • 没有“所有者绘制”,没有 P/Invoke(无论这意味着什么),没有复杂的大小/位置计算,也没有蹩脚的程序“绘制代码”。只有漂亮的声明性 XAML 和 DataBinding 才能简单、简单的属性。
  • UI 是通过使用 2 个具有不同DataTemplates的ItemsControls创建的(一个用于“背景”小时框,另一个用于预订视觉表示)
  • “已预订”文本块位于 a 内Viewbox,使其拉伸到可用大小。您可以根据需要更改它,但我想不出更好的方法来使文本适合不同预订的可用空间。
  • 我什至花时间添加了漂亮的描述性ToolTip。你真的可以在 WPF 中做你想做的事。
  • 我强烈建议你阅读这篇文章中的所有链接材料,主要是 Rachel 的“WPF Mentality”和相关的博客文章。如果您需要进一步的帮助,请告诉我。

底线:

忘记winforms吧,它太有限了,它没有(真正的)数据绑定,它需要很多代码来做更少的事情,它不支持任何级别的自定义,它迫使你创建像UI一样糟糕的Windows 95。

WPF Rocks:只需将我的代码复制并粘贴到 a 中File -> New Project -> WPF Application,然后自己查看结果。

于 2013-09-27T11:27:02.597 回答
1

评估您将花费在开发控件上的时间,将其乘以您的成本/小时,添加一些您将(肯定)产生的错误,并将其与一些现有的、经过良好测试的解决方案进行比较:

http://www.telerik.com/products/winforms/scheduler.aspx

http://www.devexpress.com/Products/NET/Controls/WinForms/Scheduler/

我建议你购买你的控制(或一些)。

于 2013-09-24T11:16:11.540 回答
1

您最好的选择是创建一个从类似控件继承的自定义控件,对于您的示例,类似于图片框的内容可能是有益的。有关自定义控件的(稍微过时的 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 坐标。

于 2013-09-24T11:45:40.147 回答