【问题标题】:How to pass InkCanvas SelectedStrokes to viewmodel?如何将 InkCanvas SelectedStrokes 传递给 viewmodel?
【发布时间】:2017-02-03 04:49:27
【问题描述】:

在标准 MVVM 模式中,视图模型如何识别来自 InkCanvas 的选定笔画?

在了解 InkCanvas 的代码隐藏中,删除选定的笔画非常容易:

 private void btnDeleteSelectedStrokes_Click(object sender, RoutedEventArgs e)
        {
            StrokeCollection selectedStrokes = theInkCanvas.GetSelectedStrokes();
            theInkCanvas.Strokes.Remove(selectedStrokes);

        }

但这可以在 MVVM 中完成吗?

TIA

【问题讨论】:

    标签: wpf mvvm inkcanvas


    【解决方案1】:

    您可以使用行为来实现相同的功能。

    public class InkCanvasDeleteBehavior : Behavior<Button>
    {
        public InkCanvas Canvas
        {
            get { return (InkCanvas)GetValue(CanvasProperty); }
            set { SetValue(CanvasProperty, value); }
        }
    
        public static readonly DependencyProperty CanvasProperty =
            DependencyProperty.Register("Canvas", 
                typeof(InkCanvas), 
                typeof(InkCanvasDeleteBehavior), 
                new PropertyMetadata(null));
    
    
    
        protected override void OnAttached()
        {
            base.OnAttached();
            var btnDelete = this.AssociatedObject as Button;
            if(btnDelete!=null)
            {
                btnDelete.Click += BtnDelete_Click;
            }
        }
    
        private void BtnDelete_Click(object sender, RoutedEventArgs e)
        {
            if(this.Canvas!=null)
            {
                var stokeCollection = this.Canvas.InkPresenter.StrokeContainer.GetStrokes();
                foreach (var stroke in stokeCollection)
                {
                    stroke.Selected = true;
                }
                this.Canvas.InkPresenter.StrokeContainer.DeleteSelected();
            }
        }
    }
    

    对于 XAML,您可以通过这种方式使用该行为。

    <Page
    x:Class="Mock.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Mock"
    xmlns:behavior="using:Mock.Behaviors"
    xmlns:i="using:Microsoft.Xaml.Interactivity"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <InkCanvas x:Name="_canvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1"/>
        <Button Content="Delete" HorizontalAlignment="Center" Margin="5">
            <i:Interaction.Behaviors>
                <behavior:InkCanvasDeleteBehavior Canvas="{Binding ElementName=_canvas}"/>
            </i:Interaction.Behaviors>
        </Button>
    </Grid>
    </Page>
    

    PS:我正在使用 UWP。在 WPF 中,有些代码可能略有不同,但底层逻辑是相同的。

    还有另一种方法可以做到这一点,通过将对象通过命令参数传递给命令,然后使用按钮中的命令删除项目。那也行。如果您需要样品,请告诉我。

    除此之外,您还可以使用行为将值传递给视图模型。您可以在视图模型中保留属性 StrokeCollection 并传递对行为的引用。当您在InkCanvas 中绘制某些内容时,更新将在ViewModel 中反映的行为中的StrokeCollection

    唯一的区别是,行为将附加到 InkCanvas 而不是示例中的 Button。

    【讨论】:

    • 我在 WPF 和 InkCanvas 中工作。我需要去办公室,但我认为我无法访问 InkPresenter 或 stroke.Selected ?
    • 是的。您可以使用行为以及附加属性。使用行为和附加属性之间的唯一区别是您使用它们的原因。例如 Grid.Row 是您附加在控件上的东西,如果该控件包含在网格中,则该控件将变为活动状态,类似于 ScrollViewer.Horizo​​ntalScrollbarVisibility 和其他控件。如果我们想在某些现有控件中添加一些功能,我们通常会使用行为。因此,我的建议是顺应行为。但是附加的财产也能很好地完成这项工作。干杯:) :) 很高兴你有一个解决方案:) :)
    【解决方案2】:

    代替更好的解决方案,这对我有用:

    XAML
    xmlns:h="clr-namespace:Notepad.Helpers" 
    
    <InkCanvas ... h:InkCanvasExtension.IsSelectionEnabled="True"
                   h:InkCanvasExtension.TheSelectedStrokes="{Binding SelectedStrokes, Mode=TwoWay}"
    

    附加属性:

    namespace Notepad.Helpers
    {
        public static class InkCanvasExtension 
        {
            /*The provider class for an attached property (even if it is not registered as a dependency property) must provide static get and set accessors 
            * that follow the naming convention Set[AttachedPropertyName] and Get[AttachedPropertyName]. These accessors are required so that the acting XAML 
            * reader can recognize the property as an attribute in XAML and resolve the appropriate types.*/
    
            #region [IsSelectionEnabled]
            public static bool GetIsSelectionEnabled(DependencyObject obj)
            {
                return (bool)obj.GetValue(IsSelectionEnabled);
            }
    
            public static void SetIsSelectionEnabled(DependencyObject obj, bool value)
            {
                obj.SetValue(IsSelectionEnabled, value);
            }
    
            // In XAML, IsSelectionEnabled = "true" will call OnIsSelectionEnabled().
            public static readonly DependencyProperty IsSelectionEnabled =
                DependencyProperty.RegisterAttached("IsSelectionEnabled",
                typeof(bool), typeof(InkCanvasExtension),
                new UIPropertyMetadata(false, OnIsSelectionEnabled));
    
    
            private static void OnIsSelectionEnabled(object sender, DependencyPropertyChangedEventArgs e)
            {
                InkCanvas ic = sender as InkCanvas;
                if (ic != null)
                {
                    // get the value of IsSelectionEnabled (which is either true or false).
                    bool isEnabled = (bool)e.NewValue;
                    if (isEnabled)
                    {
                        ic.SelectionChanged += OnSelectionChanged;
                    }
                    else
                    {
                        ic.SelectionChanged -= OnSelectionChanged;
                    }
                }
            }
    
            private static void OnSelectionChanged(object sender, EventArgs e)
            {
                // Assigning TheSelectedStrokes directly like: 
                //      TheSelectedStrokes = selectedStrokes
                // will not work and will break the binding. Must use:
                //      SetTheSelectedStrokes(ic, selectedStrokes);
    
                InkCanvas ic = sender as InkCanvas;
                StrokeCollection selectedStrokes = ic.GetSelectedStrokes();
                SetTheSelectedStrokes(ic, selectedStrokes);
            }
    
    
            #endregion
    
            #region [TheSelectedStrokes]
            public static StrokeCollection GetTheSelectedStrokes(DependencyObject obj)
            {
                return (StrokeCollection)obj.GetValue(TheSelectedStrokes);
            }
    
            public static void SetTheSelectedStrokes(DependencyObject obj, StrokeCollection value)
            {
                obj.SetValue(TheSelectedStrokes, value);
            }
    
            // by default binding works one way, i.e. loading changes from the view model, but not updating it back.
            // so must add FrameworkPropertyMetadataOptions.BindsTwoWayByDefault to send update to the viewmodel.
            public static readonly DependencyProperty TheSelectedStrokes =
                    DependencyProperty.RegisterAttached("TheSelectedStrokes",
                    typeof(StrokeCollection), typeof(InkCanvasExtension),
                    new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
            #endregion
    
        }
    
    }
    

    希望它对某人有所帮助。

    【讨论】:

      猜你喜欢
      • 2011-01-22
      • 1970-01-01
      • 2019-04-08
      • 1970-01-01
      • 2021-09-19
      • 1970-01-01
      • 1970-01-01
      • 2021-01-04
      • 2016-09-23
      相关资源
      最近更新 更多