我在一个基于 MVVM 的 WPF 应用程序中开发的时间线遇到了一个小障碍。
我的愿望是能够告诉用户希望查看什么“bin”,因为通常有比视图中显示的事件更多的事件。这本质上是一个小的堆栈面板,在代码隐藏中开发并显示在用户选择的 bin 上方。更多细节如下。
基本背景是这样的:
我正在记录 ViewModel 中发生的事件,我将它们称为事件。
我在时间轴上的视图中将它们显示为 50x50 画布(每个事件一个)。这里的细微差别是我的空间非常有限,所以我将显示的实际事件数量限制为略微偏移的 3 个堆栈(想象一下卡片堆叠在一起,显示下方卡片的顶部和右侧) 的每个时间块。
在时间的每个刻度(每 30 秒),画布向左滚动 75 个像素,并且所有绘制的元素也随之移动,当然。这也为事件设置了“垃圾箱”。基本上 0 到 29.9 秒之间的所有内容都在 bin 0 中,30 - 59.9 是 bin 1 等等。
我正在使用链接到负责显示绑定事件的 ItemsControl 的 PreviewMouseLeftButtonDown 事件。
就目前而言,一切正常。我可以收集鼠标点击,我一直在尝试使用一些基本的数学来确定点击了哪组事件。这是基于单击时鼠标的 X 位置并考虑到 ScrollViewer 窗口的任何滚动。不幸的是,我没有得到正确的“bin”。
我尝试过的其他事情:
在 canvas 元素中添加一个标签,其中包含有关它所属的 bin 的信息。这不起作用,因为我得到的代码中的 ItemsSource 包含每个事件,而不仅仅是那些被点击的事件。这就是我访问 ItemsSource 的方式。
private void ItemsControl_PreviewMouseLeftButtonDown( object sender, MouseButtonEventArds e) { var control = (ItemsControl)sender; var isource = control.ItemsSource; Debug.Assert(isource != null); }
在将其放入项目控件之前,我已尝试将其嵌套在堆栈面板中。这在我有一个嵌套列表时有效,但在我摆脱了列表列表后它就崩溃了。我也不确定这是否是一个可行的选择。
我为此拥有的 XAML(对于那些可能想要查看它的人)如下:
感兴趣的主要 ItemsControl 是最后一个,因为它包含用于在 ScrollViewer 的画布上绘制事件的数据模板。另外两个仅用于我在时间线底部使用的时间戳。
<UserControl
x:Class="Removed.Views.TransitionTimeline"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style x:Key="ISTCanvasStyle" TargetType="Canvas">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="ISTBorderStyle" TargetType="Border">
<Setter Property="BorderThickness" Value="3" />
<Setter Property="BorderBrush" Value="{Binding ColorBrush}" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Background" Value="#FF555555" />
<Setter Property="Height" Value="50" />
<Setter Property="Width" Value="50" />
</Style>
<Style x:Key="ISTTextBlockStyle" TargetType="TextBlock">
<Setter Property="Text" Value="{Binding ShortName}" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="40" />
<Setter Property="Foreground" Value="White" />
<Setter Property="TextAlignment" Value="Center" />
</Style>
<Style x:Key="EventCountTextStyle" TargetType="TextBlock">
<Setter Property="Text" Value="{Binding ExtraEvents}" />
<Setter Property="Canvas.Top" Value="-15" />
<Setter Property="Canvas.Left" Value="-25" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="13" />
<Setter Property="Foreground" Value="{Binding EventTextColorBrush}" />
<Setter Property="TextAlignment" Value="Center" />
</Style>
<DataTemplate x:Key="IndividualStateTransitions">
<Canvas Margin="{Binding Margin}" Style="{ StaticResource ISTCanvasStyle}" >
<Border Canvas.Left="-12.5" Style="{ StaticResource ISTBorderStyle}" >
<TextBlock Style="{StaticResource ISTTextBlockStyle}" />
</Border>
<TextBlock Style="{StaticResource EventCountTextStyle}" />
</Canvas>
</DataTemplate>
<DataTemplate x:Key="IST">
<ItemsControl
ItemsSource="{Binding}"
ItemTemplate="{StaticResource IndividualStateTransitions}"
PreviewMouseLeftButtonDown="ItemsControl_PreviewMouseLeftButtonDown"
Width="75">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.ZIndex" Value="{Binding ZIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</DataTemplate>
<DataTemplate x:Key="BottomTimeBar">
<Canvas>
<Line X1="{Binding DashX}" X2="{Binding DashX}" Y1="100" Y2="0" Stroke="#FF646464" StrokeThickness="1" StrokeDashArray="5" StrokeDashCap="Round" />
<TextBlock Canvas.ZIndex="-999" Width="50" TextAlignment="Center" Text="{Binding TimerText}" Canvas.Left="{Binding BlockLeft}"
Canvas.Top="85" Foreground="White" Background="#FF444444" FontSize="13" />
</Canvas>
</DataTemplate>
</UserControl.Resources>
<Grid Name="TimelineGrid" Height="192">
<Grid.RowDefinitions>
<RowDefinition Height="92" />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Canvas Grid.Row="0" Width="1920" Height="100">
<ScrollViewer
Width="1920"
Height="100"
Name="_timelineScrollViewer2"
CanContentScroll="True"
Background="Transparent"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<Canvas Width="9999" Name="_timelineCanvas2">
</Canvas>
</ScrollViewer>
</Canvas>
<Canvas Grid.Row="1" Name="MainCanvas" Width="1920" Height="100" >
<Line X1="0" X2="1920" Y1="0" Y2="0" Stroke="#FFD0D0D0" StrokeThickness="3">
<Line.Effect>
<DropShadowEffect BlurRadius="3" ShadowDepth="3" />
</Line.Effect>
</Line>
<Line X1="0" X2="1920" Y1="55" Y2="55" Stroke="#FFD0D0D0" StrokeThickness="3">
<Line.Effect>
<DropShadowEffect BlurRadius="3" ShadowDepth="3" />
</Line.Effect>
</Line>
<ScrollViewer
Width="1920"
Height="100"
Name="_timelineScrollViewer"
CanContentScroll="True"
Background="Transparent"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
PreviewMouseLeftButtonDown="TimelineScrollViewerLeftMouseDown"
PreviewMouseLeftButtonUp="LeftMouseUp"
PreviewMouseMove="TimelineScrollViewerMouseMove"
PreviewMouseWheel="TimelineScrollViewerPreviewMouseWheel"
ScrollChanged="OnTimelineScrollChanged" ClipToBounds="False">
<Canvas Width="9999" Name="_timelineCanvas">
<Line Canvas.ZIndex="-1000" Name="CurrentTimeLine" X1="1280" X2="1280" Y1="0" Y2="100" Stroke="#FFD0D0D0" StrokeThickness="3">
<Line.Effect>
<DropShadowEffect BlurRadius="3" ShadowDepth="3" />
</Line.Effect>
</Line>
<ItemsControl ItemsSource="{Binding BottomTimeBarData}" ItemTemplate="{StaticResource BottomTimeBar}" Canvas.Left="{Binding LeftScroll}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl ItemsSource="{Binding BottomTimeBarDataPast}" ItemTemplate="{StaticResource BottomTimeBar}" Canvas.Left="{Binding LeftScroll}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ItemsControl ItemsSource="{Binding DisplayObject}" ItemTemplate="{StaticResource IndividualStateTransitions}" Canvas.Left="{Binding LeftScroll}" Canvas.Top="15" Margin="0.0, 25.5"
PreviewMouseLeftButtonDown="ItemsControl_PreviewMouseLeftButtonDown" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.ZIndex" Value="{Binding ZIndex}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Canvas>
</ScrollViewer>
</Canvas>
</Grid>
</UserControl>
如果您有任何问题,请告诉我。我很乐意澄清发生了什么。最终我正在寻找的是一种确定用户希望查看的“bin”的方法。
更新 1:有关绘制对象的附加信息。
我已经定义了自己的内部类,因此我可以将它用作我的 ObservableCollection 的数据类型,这就是在这种情况下绑定的内容。
internal class DisplayObjects : INotifyPropertyChanged
{
private int _elementbin;
public int ElementBin
{
get { return _elementbin; }
set
{
_elementbin = value;
if ( PropertyChanged != null )
PropertyChanged( this, new PropertyChangedEventArgs( "ElementBin" ) );
}
}
private string _shortName;
public string ShortName
{
get { return _shortName; }
set
{
_shortName = value;
if ( PropertyChanged != null )
PropertyChanged( this, new PropertyChangedEventArgs( "ShortName" ) );
}
}
private string _margin;
public string Margin
{
get { return _margin; }
set
{
_margin = value;
if ( PropertyChanged != null )
PropertyChanged( this, new PropertyChangedEventArgs( "Margin" ) );
}
}
private string _extraevents;
public string ExtraEvents
{
get { return _extraevents; }
set
{
_extraevents = value;
if ( PropertyChanged != null )
PropertyChanged( this, new PropertyChangedEventArgs( "ExtraEvents" ) );
}
}
public Color EventTextColor { get; set; }
public SolidColorBrush EventTextColorBrush
{
get
{
return new SolidColorBrush( EventTextColor );
}
}
private int _zindex;
public int ZIndex
{
get { return _zindex; }
set
{
_zindex = value;
if ( PropertyChanged != null )
PropertyChanged( this, new PropertyChangedEventArgs( "ZIndex" ) );
}
}
public Color Color { get; set; }
public SolidColorBrush ColorBrush
{
get { return new SolidColorBrush( Color ); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
我订阅了一个事件聚合器,因此我可以在事件发送时捕获它们。这触发了我的OnEventReceived
方法。关于此方法,您需要了解的只是它从接收到的事件中获取信息,并根据事件发生的时间确定它应该落入哪个 bin。这会触发对我的DrawObject
方法的调用。
如果您想尝试模拟这一点,您可以用一些硬编码的东西替换 stvm 的使用。我无法为此提供实际数据。stvm.Color 只是表示事件类型的颜色。stvm.ShortName 只是一个字母。_bins 只是一个List<Int>
跟踪相应 bin 中发生的事件总数的方法。 position
只是它所属的 bin 中的数字事件。因此,如果这是第一个事件,那将是 1。如果这是第十个事件,它将是 10。
这里的FindLastVisualChild
方法只是为了返回给定 bin 的第三个 DisplayObject 的位置索引。这使我可以更新显示“+ X more”的标签。堆栈中的事件。
private void DrawObject( EventViewModel stvm, int position, int bin )
{
var margin = "";
var zindex = 0;
var left = bin * 75.0;
var textColor = Colors.Transparent;
var extraEvents = "+ ";
switch ( position )
{
case 1:
left += 12.5;
margin = left + ", -5";
zindex = 3;
DisplayObject.Add( new DisplayObjects
{
Color = stvm.Color,
ShortName = stvm.ShortName,
Margin = margin,
ZIndex = zindex,
EventTextColor = textColor,
ExtraEvents = extraEvents,
ElementBin = bin
} );
break;
case 2:
left += 22.5;
margin = left + ", -15";
zindex = 2;
DisplayObject.Add( new DisplayObjects
{
Color = stvm.Color,
ShortName = stvm.ShortName,
Margin = margin,
ZIndex = zindex,
EventTextColor = textColor,
ExtraEvents = extraEvents,
ElementBin = bin
} );
break;
case 3:
left += 32.5;
margin = left + ", -25";
zindex = 1;
DisplayObject.Add( new DisplayObjects
{
Color = stvm.Color,
ShortName = stvm.ShortName,
Margin = margin,
ZIndex = zindex,
EventTextColor = textColor,
ExtraEvents = extraEvents,
ElementBin = bin
} );
break;
default:
//left += 32.5;
//margin = left + ", -25";
//DisplayObject.Add( new DisplayObjects { Color = stvm.Color, ShortName = stvm.ShortName, Margin = margin, ZIndex = zindex, EventTextColor = textColor, ExtraEvents = extraEvents } );
extraEvents += ( _bins[bin] - 3 ) + " more.";
var test = FindLastVisualChild( bin );
DisplayObject[test].EventTextColor = Colors.White;
DisplayObject[FindLastVisualChild( bin )].ExtraEvents = extraEvents;
break;
}
}
private int FindLastVisualChild( int bin )
{
var sum = 0;
for ( var idx = 0; idx <= bin; idx++ )
if ( _bins[idx] <= 3 )
sum += _bins[idx];
else
sum += 3;
return ( sum - 1 );
}