我正在尝试使用基于MVVM的WPF应用程序进行开发的时间轴。
我的愿望是能够分辨用户希望查看的“bin”,因为通常会有比View中显示的事件更多的事件。这基本上是一个小的小堆栈面板,它在代码隐藏中开发并显示在用户选择的bin上方。更多细节如下。
基本背景是:
我正在记录ViewModel中发生的事件,我将它们称为事件。
我在时间轴上的视图中将它们显示为50x50画布(每个事件一个)。这里的细微差别在于我的空间非常有限,所以我将显示的事件的实际数量限制为3个堆叠稍微偏移(只是想象卡片堆叠在彼此后面,卡片的顶部和右部在下面显示)每个时间块的彼此。
在每个时间刻度(每30秒),画布向左滚动75个像素,当然所有绘制的元素也随之移动。这也为事件设置了“箱子”。基本上0到29.9秒之间的所有内容都在bin 0中,30 - 59.9是bin 1,依此类推。
我正在使用一个PreviewMouseLeftButtonDown事件,该事件链接到负责显示绑定事件的ItemsControl。
现在一切正常。我可以收集鼠标点击,我一直在尝试使用一些基本的数学来确定点击了哪组事件。这是基于点击时鼠标的X位置并考虑到ScrollViewer窗口的任何滚动。不幸的是,我没有得到一个正确的“垃圾桶”。
我尝试过的其他事情:
在canvas元素中添加一个标记,其中包含有关它所属的bin的信息。这不起作用,因为我得到的代码中的ItemsSource包含每个事件,而不仅仅是那些被点击的事件。这就是我访问ItemsSource的方式。
private void ItemsControl_PreviewMouseLeftButtonDown( object sender, MouseButtonEventArds e)
{
var control = (ItemsControl)sender;
var isource = control.ItemsSource;
Debug.Assert(isource != null);
}
我已尝试将其嵌入到stackpanel中,然后再将其放入itemscontrol中。当我有一个嵌套列表但是在我离开列表列表之后它就崩溃了。我不确定此时它是否也是一个可行的选择。
我所拥有的XAML(对于那些可能想要看到它的人)如下:
感兴趣的主要ItemsControl是最后一个,因为它包含用于在ScrollViewer中的画布上绘制事件的datatemplate。另外两个仅用于我在时间线底部使用的时间戳。
<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的第3个DisplayObject的位置索引。这允许我更新标记“更多+ + X”。堆栈中的事件。
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 );
}
答案 0 :(得分:1)
您是否尝试过使用e.OriginalSource
?
我显然无法重现您的项目,但据我所知,您应该能够通过使用此单击来获取Canvas:
var canvas = e.OriginalSource as Canvas
然后你应该能够轻松获得相应的项目,因为我理解它是画布的DataContext:
var item = canvas.DataContext as MyItemViewModel;
但正如我所写,这是猜测。您应该为我们发布更多信息,以便在您的应用中看到更清晰的信息,例如您将项目添加到集合中的代码。
答案 1 :(得分:0)
非常感谢David和Rachel在这里。他们的投入使我得到了答案,我终于开始工作了。
就像参考此后出现并阅读它的人一样。这就是我所做的。
在我在Canvas上IndividualStateTransistions
添加的Tag = "{Binding ElementBin}"
的数据模板中。然后我添加了bin元素,该元素属于将事件过滤到bin中的代码。
根据Rachel的建议,我将鼠标点击事件移动到此Canvas。
在后面的代码中,我改编了大卫建议的内容:
在OnMouseClickEvent方法中......
var control = (Canvas)sender;
var item = control.DataContext as DisplayObjects;
var itembin = item.ElementBin;
这给了我那个事件组所属的bin,并允许我继续下一步。
再次感谢你们(和女士们)!