如何将元素放在 Canvas 上并将其位置绑定到 Avalonia 中元素的 viewModel

问题描述 投票:0回答:1

我正在尝试创建一个编辑器,您可以在其中将预定义的形状放置到画布上,然后与它们交互。我了解如何使用

Canvas.SetTop()
方法将形状放在画布上的特定位置。现在我不知道如何在渲染后更改形状的位置。我正在尝试遵守 MVVM 规则。到目前为止我已经创建了这个:

MainWindow(菜单并不重要,但发布以防万一):

<DockPanel LastChildFill="True">
        <Menu DockPanel.Dock="Top">
            <MenuItem Header="File">
                <MenuItem Header="Exit" />
            </MenuItem>
            <MenuItem Header="Action">
                <MenuItem Header="Draw place" />
                <MenuItem Header="Draw transition" />
                <MenuItem Header="Draw arc" />
                <MenuItem Header="Play" />
            </MenuItem>
        </Menu>

        <Canvas Name="MyCanvas" PointerReleased="Canvas_OnPointerReleased" Background="#141414" Margin="10" />
    </DockPanel>

MainWindow 背后代码:

private void Canvas_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
    {
        var viewModel = DataContext as MainWindowViewModel;
        var clickPosition = e.GetPosition(MyCanvas);
        viewModel?.CreateTransitionCommand.Execute(clickPosition);
    }

主窗口视图模型:

public partial class MainWindowViewModel : ObservableObject
{
    private ObservableCollection<NodeViewModelBase> Elements { get; } = new ObservableCollection<NodeViewModelBase>();
    
    [RelayCommand]
    private void CreateTransition(Point position)
    {
        var model = new Transition();
        var viewModel = new TransitionViewModel(model) {X = position.X, Y = position.Y};
        Elements.Add(viewModel);
    }
}

Model Transition
我想这里并不重要,但我会展示
TransitionViewModel
以及
TransitionView

TransitionViewModel:

public partial class TransitionViewModel : NodeViewModelBase
{
    public TransitionViewModel(Transition transition)
    {
        Transition = transition;
    }

    private Transition Transition { get; set; }
    
    [ObservableProperty]
    private double _x;
    
    [ObservableProperty]
    private double _y;

    [ObservableProperty]
    private string _borderBrush = "#444444";

    [ObservableProperty] 
    private int _size = 40;

    [ObservableProperty]
    private string _name = "Transition";

    [ObservableProperty] 
    private int _fontSize = 10;
}

过渡视图:

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:vm="using:Editor.ViewModels"
             mc:Ignorable="d"
             x:Class="Editor.Views.TransitionView"
             x:DataType="vm:TransitionViewModel">

    <StackPanel>
        <Border BorderBrush="{Binding BorderBrush}" BorderThickness="1" HorizontalAlignment="Center">
            <Rectangle Fill="#141414" Height="{Binding Size}" Width="{Binding Size}" />
        </Border>
        <TextBlock Text="{Binding Name}" FontSize="{Binding FontSize}" HorizontalAlignment="Center" Margin="0, 3, 0, 0" />
    </StackPanel>
</UserControl>

现在的问题是,如何高效、简单地将这个TransitionView添加到Canvas中。当然,我尝试跳过在

MainWindow code behind
中执行的命令,而是仅创建一个视图,设置其数据上下文,然后将其添加到画布。但我没有找到任何方法,如何在离开事件处理程序方法后更改其在画布上的位置。

所以我尝试寻找解决方案。起初,我尝试更改命令,因此它所做的只是向集合中添加一个元素。其他一切都由隐藏代码处理,以使其更加简单。我尝试设置属性并将它们绑定在我的

TransitionView
中,但它不起作用:

<UserControl.Styles>
        <Style Selector="UserControl">
            <Setter Property="Canvas.Left" Value="{Binding X}"/>
            <Setter Property="Canvas.Top" Value="{Binding Y}"/>
        </Style>
    </UserControl.Styles>

后来,我在互联网上发现,对于某些人来说 ItemsControl 有效。我尝试了多种方法。我得到的只是在屏幕上写的“Avalonia.Markup.Xaml.Templates.ItemsPanelTemplate”。这肯定表明它的实施是错误的,但我无法修复它。正如我所说,我尝试了多种方法,这是我的最后一种:

<Window.DataTemplates>
        <DataTemplate DataType="vm:TransitionViewModel">
            <views:TransitionView />
        </DataTemplate>
    </Window.DataTemplates>


<Canvas Name="MyCanvas" PointerReleased="Canvas_OnPointerReleased" Background="#141414" Margin="10">
            <ItemsControl ItemsSource="{Binding Elements}">
                <ItemsPanelTemplate>
                    <Canvas />
                </ItemsPanelTemplate>

                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <ContentControl Content="{Binding}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Canvas>

我尝试使用

ContentControl
的原因是我不需要在画布上只绘制矩形(TransitionView),还需要绘制圆形。所以我尝试创建一个系统,它可以识别 ItemsSource 中的每个元素应该如何在画布上绘制。


我认为值得一提的是,我确实能够找到某种解决方案,但它并不理想,而且也无法发挥应有的作用。我尝试在

PointerRelease
中添加
TransitionView
监听器:

<StackPanel PointerReleased="InputElement_OnPointerReleased">
        <Border BorderBrush="{Binding BorderBrush}" BorderThickness="1" HorizontalAlignment="Center">
            <Rectangle Fill="#141414" Height="{Binding Size}" Width="{Binding Size}" />
        </Border>
        <TextBlock Text="{Binding Name}" FontSize="{Binding FontSize}" HorizontalAlignment="Center" Margin="0, 3, 0, 0" />
    </StackPanel>

之后,在后面的代码中,我只是使用

Canvas.setTop(this, 100)
Canvas.setLeft(this, 100)
来确定它是否有效。令我惊讶的是,它成功了,并将画布上的矩形移动到了点 100,100。但是我的鼠标点击不仅被 TransitionView 中的 StackPanel 识别,而且还被 canvas 本身识别。所以我移动了我的视图,但也在原来的同一位置创建了一个新视图。我无法找到解决此问题的正确解决方案,但这可能是一种方法。


我的方法可能完全错误,我愿意接受任何建议。我对 Avalonia 很陌生,基本上对任何使用 MVVM 原则且其视图是用 XAML 编写的 UI 框架都很陌生。我正在寻找最优雅、最清晰、对委托人友好的解决方案。

c# canvas mvvm avaloniaui avalonia
1个回答
0
投票

尝试探索此存储库。另外,请检查可移动控件的此示例

就我的经验而言,我使用了第二个示例,但它不是一个简单的矩形,而是“DisplayControl” - 一个元素,它基于绑定视图模型创建其内容。在主视图上,我有一个集合视图,并将 DisplayControl 设置为模板。在我的主视图模型中,我有一个 Elements_ViewModel 列表。 当我想添加一个窗口时,我可以在列表中添加新的视图模型 - 集合将自动更新并绘制新窗口。

希望有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.