Access XAML object in ViewModel

16,326

Solution 1

You shouldn't do this (at least, this is definitely not a MVVM-way).
Use IsModal property and data binding instead:

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

where IsModal in the binding expression is a bound data property within your view model.

Solution 2

In MVVM, any data going back and forth should be done via data binding properties on your View Model. That includes virtually any properties of the ContentPresenter - just add a binding. However, if you want call a method of a XAML object in an MVVM-friendly way, you can use an Action.

Let's suppose I wanted to set the focus on a textbox via the view model (contrived example, I know there's other MVVM-ways to do this- just wanted an example that requires a child object of the view).

First, give the textbox that should get the focus a name in your XAML, i.e.,

<TextBox x:Name="textBox1"/>

On your view model, add a property for the Action:

public Action FocusAction {get;set;}

Some time before or as your view is loading, get your DataContext (i.e., your view model) and add the Action to the code behind (the view's .cs file):

ViewModel vm = (ViewModel)this.DataContext;

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

For instance, you might implement the IsLoaded event and do it there. You could also do this in your constructor after InitializeComponent, as long as it either created your view model instance or it was passed in as a paramater. The key is that the view model is already instantiated and assigned to the view's data context.

Then in your view model, wherever you wanted to add focus to that textbox, call:

FocusAction();

Since what I just showed above requires that your view cast the DataContext to a particular view model, what I do is create an interface for the kinds of actions I need, like this:

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

Then I make my view model implement that inteface. With that done, in my view's code-behind, I can say something like:

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

I think that makes it more MVVM-compliant, since it's not tightly-coupled to a particular view model, the view just knows it can add an action if the view model is the type of view model that can use it.

More details and another example available here: http://jkshay.com/closing-a-wpf-window-using-mvvm-and-minimal- code-behind/

Solution 3

I know it's been few years but I just faced the same, so here's my answer in case someone will find it useful... In your xaml file refer to your ViewModel class in the DataContext:

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

In your ViewModel class create a public function with your xaml file as argument and a private member to hold it:

private MainWindow mainWindow;

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

In your code behind use the function pass the the 'yourViewModel' you defined in the xaml:

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

And that's it :) Now you can access all your xaml elements from your ViewModel by using your mainWindow member, just give them a name.

mainWindow.textBox1
Share:
16,326
Patrick Vogt
Author by

Patrick Vogt

I am a passionate programmer and computer scientist. In my free time, I am committed to the Volunteer Fire Brigade and in profession, I am responsible for data transmission in the salary section. There, I develop Java and COBOL applications for IBM mainframes.

Updated on June 04, 2022

Comments

  • Patrick Vogt
    Patrick Vogt almost 2 years

    How could I access a XAML object in my ViewModel? I am really confused. I want to access the <Controls:ModalContentPresenter> object. How could I realise this in a MVVM conform way? On this object I want to call a method 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>
    

    The code of ModalContentPresenter can be found here:

    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
        }
    }
    
  • Benjamin Gale
    Benjamin Gale over 9 years
    I am the creator of the referenced ModalContentPresenter (see here and here) and can confirm that this is the way the control was designed to be used in an MVVM compliant manner.