【问题标题】:Access XAML object in ViewModel在 ViewModel 中访问 XAML 对象
【发布时间】:2014-09-26 23:21:45
【问题描述】:

如何访问我的 ViewModel 中的 XAML 对象?我真的很困惑。我想访问<Controls:ModalContentPresenter> 对象。我怎么能以符合 MVVM 的方式实现这一点?在这个对象上我想调用一个方法ShowModalContent

<Controls:ModalContentPresenter x:Name="modalContent">
    <ScrollViewer Behaviors:AdvancedZooming.KeepInCenter="true" Visibility="{Binding LeerformularIsVisible}" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
        <Viewbox Stretch="Uniform">
            <Grid>
                <DataGrid BorderBrush="{x:Null}">
                    <DataGrid.ContextMenu>
                        <ContextMenu>
                            <MenuItem Command="{Binding AddFieldDefinitionCommand}" Header="Feld hinterlegen" Icon="pack://application:,,,/Images/Designer/field.png" />
                            <MenuItem Command="{Binding AddFunctionCommand}" Header="Funktion hinterlegen" Icon="pack://application:,,,/Images/Designer/FI_Taschenmesser_16x16.png" />
                            <MenuItem Command="{Binding RemoveFieldDefinitionCommand}" Header="Aktuelle Felddefinition entfernen" Icon="pack://application:,,,/Images/Designer/remove_field.png" />
                            <MenuItem Command="{Binding CutCommand}" Header="Ausschneiden" Icon="pack://application:,,,/Images/Zwischenablage/FI_Ausschneiden_16x16.png" />
                            <MenuItem Command="{Binding CopyCommand}" Header="Kopieren" Icon="pack://application:,,,/Images/Zwischenablage/FI_Kopieren_16x16.png" />
                            <MenuItem Command="{Binding PasteCommand}" Header="Einfügen" Icon="pack://application:,,,/Images/Zwischenablage/FI_Einfuegen_16x16.png" />
                        </ContextMenu>
                    </DataGrid.ContextMenu>

                </DataGrid>
            </Grid>

        </Viewbox>

    </ScrollViewer>

    <Controls:ModalContentPresenter.ModalContent>
        <StackPanel>
            <TextBlock>Test</TextBlock>
            <Button>Hide</Button>
        </StackPanel>
    </Controls:ModalContentPresenter.ModalContent>
</Controls:ModalContentPresenter>

ModalContentPresenter的代码可以在这里找到:

using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;

namespace Controls
{
    [ContentProperty("Content")]
    public class ModalContentPresenter : FrameworkElement
    {
        #region private fields
        private Panel layoutRoot;
        private ContentPresenter primaryContentPresenter;
        private ContentPresenter modalContentPresenter;
        private Border overlay;
        private object[] logicalChildren;
        private KeyboardNavigationMode cachedKeyboardNavigationMode;
        private static readonly TraversalRequest traversalDirection;
        #endregion

        #region dependency properties
        public static readonly DependencyProperty IsModalProperty = DependencyProperty.Register("IsModal", typeof(bool), typeof(ModalContentPresenter),
            new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsModalChanged));

        public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(ModalContentPresenter),
        new UIPropertyMetadata(null, OnContentChanged));

        public static readonly DependencyProperty ModalContentProperty = DependencyProperty.Register("ModalContent", typeof(object), typeof(ModalContentPresenter),
        new UIPropertyMetadata(null, OnModalContentChanged));

        public static readonly DependencyProperty OverlayBrushProperty = DependencyProperty.Register("OverlayBrush", typeof(Brush), typeof(ModalContentPresenter), 
        new UIPropertyMetadata(new SolidColorBrush(Color.FromArgb(204, 169, 169, 169)), OnOverlayBrushChanged));

        public bool IsModal
        {
            get { return (bool)GetValue(IsModalProperty); }
            set { SetValue(IsModalProperty, value); }
        }

        public object Content
        {
            get { return (object)GetValue(ContentProperty); }
            set { SetValue(ContentProperty, value); }
        }

        public object ModalContent
        {
            get { return (object)GetValue(ModalContentProperty); }
            set { SetValue(ModalContentProperty, value); }
        }

        public Brush OverlayBrush
        {
            get { return (Brush)GetValue(OverlayBrushProperty); }
            set { SetValue(OverlayBrushProperty, value); }
        } 
        #endregion

        #region routed events
        public static readonly RoutedEvent PreviewModalContentShownEvent = EventManager.RegisterRoutedEvent("PreviewModalContentShown", RoutingStrategy.Tunnel,
        typeof(RoutedEventArgs), typeof(ModalContentPresenter));

        public static readonly RoutedEvent ModalContentShownEvent = EventManager.RegisterRoutedEvent("ModalContentShown", RoutingStrategy.Bubble,
        typeof(RoutedEventArgs), typeof(ModalContentPresenter));

        public static readonly RoutedEvent PreviewModalContentHiddenEvent = EventManager.RegisterRoutedEvent("PreviewModalContentHidden", RoutingStrategy.Tunnel, 
        typeof(RoutedEventArgs), typeof(ModalContentPresenter));

        public static readonly RoutedEvent ModalContentHiddenEvent = EventManager.RegisterRoutedEvent("ModalContentHidden",  RoutingStrategy.Bubble, 
        typeof(RoutedEventArgs), typeof(ModalContentPresenter));

        public event RoutedEventHandler PreviewModalContentShown
        {
            add { AddHandler(PreviewModalContentShownEvent, value); }
            remove { RemoveHandler(PreviewModalContentShownEvent, value); }
        }

        public event RoutedEventHandler ModalContentShown
        {
            add { AddHandler(ModalContentShownEvent, value); }
            remove { RemoveHandler(ModalContentShownEvent, value); }
        }

        public event RoutedEventHandler PreviewModalContentHidden
        {
            add { AddHandler(PreviewModalContentHiddenEvent, value); }
            remove { RemoveHandler(PreviewModalContentHiddenEvent, value); }
        }

        public event RoutedEventHandler ModalContentHidden
        {
            add { AddHandler(ModalContentHiddenEvent, value); }
            remove { RemoveHandler(ModalContentHiddenEvent, value); }
        }
        #endregion

        #region ModalContentPresenter implementation
        static ModalContentPresenter()
        {
            traversalDirection = new TraversalRequest(FocusNavigationDirection.First);
        }

        public ModalContentPresenter()
        {
            layoutRoot = new ModalContentPresenterPanel();
            primaryContentPresenter = new ContentPresenter();
            modalContentPresenter = new ContentPresenter();
            overlay = new Border();

            AddVisualChild(layoutRoot);

            logicalChildren = new object[2];

            overlay.Background = OverlayBrush;
            overlay.Child = modalContentPresenter;
            overlay.Visibility = Visibility.Hidden;

            layoutRoot.Children.Add(primaryContentPresenter);
            layoutRoot.Children.Add(overlay);
        }

        public void ShowModalContent()
        {
            if (!IsModal)
                IsModal = true;
        }

        public void HideModalContent()
        {
            if (IsModal)
                IsModal = false;
        }

        private void RaiseModalContentShownEvents()
        {
            RoutedEventArgs args = new RoutedEventArgs(PreviewModalContentShownEvent);
            OnPreviewModalContentShown(args);
            if (!args.Handled)
            {
                args = new RoutedEventArgs(ModalContentShownEvent);
                OnModalContentShown(args);
            }
        }

        private void RaiseModalContentHiddenEvents()
        {
            RoutedEventArgs args = new RoutedEventArgs(PreviewModalContentHiddenEvent);
            OnPreviewModalContentHidden(args);
            if (!args.Handled)
            {
                args = new RoutedEventArgs(ModalContentHiddenEvent);
                OnModalContentHidden(args);
            }
        }

        protected virtual void OnPreviewModalContentShown(RoutedEventArgs e)
        {
            RaiseEvent(e);
        }

        protected virtual void OnModalContentShown(RoutedEventArgs e)
        {
            RaiseEvent(e);
        }

        protected virtual void OnPreviewModalContentHidden(RoutedEventArgs e)
        {
            RaiseEvent(e);
        }

        protected virtual void OnModalContentHidden(RoutedEventArgs e)
        {
            RaiseEvent(e);
        }
        #endregion

        #region property changed callbacks
        private static void OnIsModalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ModalContentPresenter control = (ModalContentPresenter)d;

            if ((bool)e.NewValue == true)
            {
                control.cachedKeyboardNavigationMode = KeyboardNavigation.GetTabNavigation(control.primaryContentPresenter);
                KeyboardNavigation.SetTabNavigation(control.primaryContentPresenter, KeyboardNavigationMode.None);

                control.overlay.Visibility = Visibility.Visible;
                control.overlay.MoveFocus(traversalDirection);

                control.RaiseModalContentShownEvents();
            }
            else
            {
                control.overlay.Visibility = Visibility.Hidden;

                KeyboardNavigation.SetTabNavigation(control.primaryContentPresenter, control.cachedKeyboardNavigationMode);
                control.primaryContentPresenter.MoveFocus(traversalDirection);

                control.RaiseModalContentHiddenEvents();
            }
        }

        private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ModalContentPresenter control = (ModalContentPresenter)d;

            if (e.OldValue != null)
                control.RemoveLogicalChild(e.OldValue);

            control.primaryContentPresenter.Content = e.NewValue;
            control.AddLogicalChild(e.NewValue);
            control.logicalChildren[0] = e.NewValue;
        }

        private static void OnModalContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ModalContentPresenter control = (ModalContentPresenter)d;

            if (e.OldValue != null)
                control.RemoveLogicalChild(e.OldValue);

            control.modalContentPresenter.Content = e.NewValue;
            control.AddLogicalChild(e.NewValue);
            control.logicalChildren[1] = e.NewValue;
        }

        private static void OnOverlayBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ModalContentPresenter control = (ModalContentPresenter)d;
            control.overlay.Background = (Brush)e.NewValue;
        }
        #endregion

        #region FrameworkElement overrides
        protected override Visual GetVisualChild(int index)
        {
            if (index < 0 || index > 1)
                throw new ArgumentOutOfRangeException("index");

            return layoutRoot;
        }

        protected override int VisualChildrenCount
        {
            get { return 1; }
        }

        protected override IEnumerator LogicalChildren
        {
            get { return logicalChildren.GetEnumerator(); }
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            layoutRoot.Arrange(new Rect(finalSize));
            return finalSize;
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            layoutRoot.Measure(availableSize);
            return layoutRoot.DesiredSize;
        }
        #endregion

        #region layout panel
        class ModalContentPresenterPanel : Panel
        {
            protected override Size MeasureOverride(Size availableSize)
            {
                Size resultSize = new Size(0, 0);

                foreach (UIElement child in Children)
                {
                    child.Measure(availableSize);
                    resultSize.Width = Math.Max(resultSize.Width, child.DesiredSize.Width);
                    resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);
                }

                return resultSize;
            }

            protected override Size ArrangeOverride(Size finalSize)
            {
                foreach (UIElement child in InternalChildren)
                {
                    child.Arrange(new Rect(finalSize));
                }

                return finalSize;
            }
        }
        #endregion
    }
}

【问题讨论】:

  • 这不是 MVVM,所以随心所欲。

标签: c# wpf xaml mvvm modal-dialog


【解决方案1】:

您可以为视图模型创建一个构造函数,该构造函数接受 ContentPage 作为其参数。

public class ViewModelClass 
{
public ViewModelClass(ContentPage p=null)
{...}
}

然后在 Contentpage 返回代码脚本中设置绑定上下文,将 contentpage 的引用传递给 viewmodel

public class ContentPageClass : ContentPage
{
public ContentPageClass()
{
BindingContext = new ViewModelClass(p:this); 
}
}

【讨论】:

    【解决方案2】:

    我知道这已经有几年了,但我只是面临同样的问题,所以这是我的答案,以防有人会发现它有用...... 在您的 xaml 文件中,请参阅 DataContext 中的 ViewModel 类:

    <Window.DataContext>
        <local:YourViewModel x:Name="yourViewModel"/>
    </Window.DataContext>
    

    在您的 ViewModel 类中,使用您的 xaml 文件作为参数创建一个公共函数,并使用一个私有成员来保存它:

    private MainWindow mainWindow;
    
    public void OnViewInitialized(MainWindow mainWindow)
    {
        this.mainWindow = mainWindow;
    }
    

    在您后面的代码中使用该函数传递您在 xaml 中定义的“yourViewModel”:

    public MainWindow()
    {
        InitializeComponent();
        yourViewModel.OnViewInitialized(this);
    }
    

    就是这样:) 现在,您可以使用 mainWindow 成员从 ViewModel 访问所有 xaml 元素,只需为它们命名。

    mainWindow.textBox1
    

    【讨论】:

      【解决方案3】:

      在 MVVM 中,任何来回传输的数据都应通过视图模型上的数据绑定属性来完成。这几乎包括ContentPresenter 的任何属性——只需添加一个绑定。但是,如果您想以对 MVVM 友好的方式调用 XAML 对象的方法,则可以使用 Action

      假设我想通过视图模型将焦点设置在文本框上(人为的示例,我知道还有其他 MVVM 方法可以做到这一点 - 只是想要一个需要视图子对象的示例)。

      首先,在 XAML 中为应该获得焦点的文本框命名,即,

      <TextBox x:Name="textBox1"/>
      

      在您的视图模型上,为 Action 添加一个属性:

      public Action FocusAction {get;set;}
      

      在加载视图之前或加载时,获取 DataContext(即视图模型)并将 Action 添加到后面的代码(视图的 .cs 文件):

      ViewModel vm = (ViewModel)this.DataContext;
      
      if ( vm.FocusAction == null )
          vm.FocusAction= new Action(() => this.textBox1.Focus());
      

      例如,您可以实现 IsLoaded 事件并在那里执行。您也可以在 InitializeComponent 之后的构造函数中执行此操作,只要它创建了您的视图模型实例或作为参数传入即可。关键是视图模型已经实例化并分配给视图的数据上下文。

      然后在您的视图模型中,无论您想将焦点添加到该文本框的何处,调用:

      FocusAction();
      

      由于我刚刚在上面展示的内容要求您的视图将 DataContext 转换为特定的视图模型,因此我要做的是为我需要的各种操作创建一个接口,如下所示:

      interface IFocusable
      {
           Action FocusAction {get;set;}
      }
      

      然后我让我的视图模型实现该接口。完成后,在我看来的代码隐藏中,我可以这样说:

      if(this.DataContext is IFocusable)
          ((IFocusable)this.DataContext).FocusAction = new Action(() => this.textBox1.Focus());
      

      我认为这使它更符合 MVVM,因为它没有与特定的视图模型紧密耦合,如果视图模型是可以使用它的视图模型类型,视图就知道它可以添加一个动作。

      更多细节和另一个例子在这里:http://jkshay.com/closing-a-wpf-window-using-mvvm-and-minimal- 代码隐藏/

      【讨论】:

        【解决方案4】:

        您不应该这样做(至少,这绝对不是 MVVM 方式)。
        请改用IsModal 属性和数据绑定:

        <Controls:ModalContentPresenter x:Name="modalContent" IsModal="{Binding IsModal}">
        

        绑定表达式中的IsModal 是视图模型中的绑定数据属性。

        【讨论】:

        • 我是引用的ModalContentPresenter(请参阅herehere)的创建者,并且可以确认这是设计用于以符合MVVM 的方式使用控件的方式。
        猜你喜欢
        • 2012-10-30
        • 1970-01-01
        • 2012-07-13
        • 1970-01-01
        • 2019-04-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多