使用ItemsControl作爲容器
想要在面板上顯示圖形,先把圖形的VM都創建好,希望能傳所有VM到一個列表(ObservableCollection
泛型容器)裏,然後界面上就能顯示出對應的圖形。
這些圖形VM都繼承自ViewModelBase
,同時有一些是結點, 有一些是連線。結點是直接繼承了NetworkItem_VM
,然後再間接繼承ViewModelBase
的。
如果直接用ItemsControl
作爲VM容器,即:
<ItemsControl Items="{Binding UserControlVMs}" Height="1000" Width="2000"/>
設置Style和數據模板:
<UserControl.Styles>
<!--ItemsControl的樣式-->
<Style Selector="ItemsControl">
<!--使用Canvas作面板-->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<!--這裏需要爲Canvas設置顏色才能在按下時相應鼠標事件-->
<Canvas Background="#EEEEEE" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<!--右鍵菜單行爲-->
<Canvas.ContextMenu>
<ContextMenu>
<MenuItem Header="xxx" Command="{Binding xxx}"/>
</ContextMenu>
</Canvas.ContextMenu>
</Canvas>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!--裏面放圖形的VM-->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate DataType="vm:ViewModelBase">
<ContentControl Content="{Binding .}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<!--Avalonia的ItemsControl中沒有ItemContainerStyle,用選擇器+樣式來綁定Canvas.Left等附加屬性-->
<Style Selector="ItemsControl > ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</UserControl.Styles>
這種方法能實現需求,但是出來的圖形會有些卡頓,僅僅在當前圖形面板上操作時不太能感覺出來,但是在不同圖形面板之間切換時就會有很明顯的卡頓,創建12個結點,40多個控制點,然後所有這些50多個點連線在一起,切換回這個圖形面板時大約需要3秒的時間(8G內存64位win10集成顯卡)。
改用ListBox作爲容器
在這篇問答中討論的是WPF上的切換問題,Avalonia中也有這樣的問題,然後答主意思是ListBox
支持UI虛擬化,代替原生的ItemsControl
效果會好一些。
改用ListBox
作爲VM容器:
<ListBox Name="panel" Items="{Binding UserControlVMs}" Height="1000" Width="2000"/>
設置Style和數據模板:
<UserControl.Styles>
<!--ListBox的樣式-->
<Style Selector="ListBox#panel">
<!--使用Canvas作面板-->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<!--這裏需要爲Canvas設置顏色才能在按下時相應鼠標事件-->
<Canvas Background="#EEEEEE" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<!--右鍵菜單行爲-->
<Canvas.ContextMenu>
<ContextMenu>
<MenuItem Header="xxx" Command="{Binding xxx"/>
</ContextMenu>
</Canvas.ContextMenu>
</Canvas>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!--裏面放圖形的VM-->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate DataType="vm:ViewModelBase">
<ContentControl Content="{Binding .}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<!--ListBox下的ListBoxItem項-->
<Style Selector="ListBox#panel ListBoxItem">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</UserControl.Styles>
這裏用ListBox#panel
指明name的原因是圖形裏面有些也帶ListBox
(狀態機的轉移邊),這裏讓選擇器只選擇這個容器ListBox
。現在渲染速度比用ItemsControl
快了大概三倍,和上面一樣多的圖形大概要用1秒的時間。
但是這種方式又帶來一個問題,就是因爲ListBox
有指針停留和選中的問題,其中的項會產生灰色不透明和藍色透明的背景,而且會影響下層內容的點擊,問題效果和可視樹如下:
如果用Enabled="False"
或者IsHitTestVisible="False"
確實可以解決這個問題,但是這樣整個ListBox
就不響應鼠標操作了,對圖形的拖拽和打開右鍵菜單都會失效。
這裏加了一個Style來解決這個問題,判斷是NetWorkItem_VM
的子類就設置IsHitTestVisible="True"
,否則就是圖形連線,設置爲False
。
<Style Selector="ListBox#panel ListBoxItem > ContentPresenter">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="IsHitTestVisible" Value="{Binding IsNetWorkItemVM}"/>
</Style>
相應地,在ViewModelBase
類裏要加個判斷的屬性:
namespace sbid._VM
{
public class ViewModelBase : ReactiveObject
{
// 判斷這個ViewModelBase對象是不是一個NetWorkItem_VM
// 用於在狀態機中區分"線"(直線/箭頭)和"元素"(狀態/轉移塊/控制點)
// 以將"線"設置不可點擊也不會顯示選中效果,防止影響到"元素"的顯示和移動
public bool IsNetWorkItemVM { get => this is NetworkItem_VM; }
}
}