36

I have a WPF Window, and in that window I have a grid.

I use M-V-VM model and I want to add a TextBox to the grid dynamically in code(in viewmodel)

How can I get access to the grid?

Ting
  • 1,658
  • 16
  • 26
Sanjay Patel
  • 955
  • 1
  • 8
  • 22
  • 3
    Before going down this route I would ensure that it is absolutely neccessary, typically its bad practice to access the view from the view model unless there is absolutely no other alternative. – BenjaminPaul Jan 09 '13 at 14:34
  • 1
    What @BenjaminPaul said. You don't add controls from the ViewModel when using MVVM. Use Binding to get this done. – Ralf de Kleine Jan 09 '13 at 14:37
  • 3
    Yes, I am aware of that, its precisely what I meant. I have however on occasion needed to gain a reference to a control within a view model to cater for controls that did not support conventional binding. Thats the scenario I was referring too. – BenjaminPaul Jan 09 '13 at 14:39

4 Answers4

74

Use Supervising Controller pattern.

Reading:

Example implementation for CaliburnMicro MVVM framework is shown here (will work same for all other frameworks - or you can do it by hand if you are doing MVVM by yourself):

http://drc.ideablade.com/devforce-2012/bin/view/Documentation/cocktail-tutorial-talk-to-view

Example:

1) Define interface IView in which ViewModel (VM) will talk to View with the required method(s)

public interface IView 
{
    void AddTextBoxToGrid();
}

2) Inherit code behind View from your IView and implement IView.AddTextboxToGrid() method

public partial class View: IView 
{
    public void AddTextBoxToGrid() 
    {  
        // implement here your custom view logic using standard code behind; 
    }
}

3) Add a property of type IView to your VM

public class ViewModel 
{
    public IView View { get; set; }
}

4) Set View property on VM to an instance of View as IView e.g. in code behind:

 DataContext.View = this as IView; 

or in Caliburn you can use IScreen.OnViewAttached override method)

public partial class View: IView 
{
    public View()
    {
        // access you VM by the strategy of your framework or choice - this example is when you store your VM in View's DataContext
        (DataContext as ViewModel).View = this as IView;
    } 

    public void AddTextBoxToGrid() 
    {  
        // implement here your custom view logic using standard code behind; 
    }
}

5) In your VM call IView.AddTextboxToGrid()

public class ViewModel 
{
    public IView View { get; set; }

    public void AddTextBoxToGrid() 
    {
        if (View == null) return;
        View.AddTextBoxToGrid()
    }
}
Ehsan
  • 767
  • 7
  • 18
nihique
  • 5,738
  • 2
  • 25
  • 27
  • 3
    It is better to use constructor injection to send the `IVew` instance to `ViewModel` (I mean, `ViewModel` class' constructor should accept an `IView` instance). – Mohammad Dehghan Oct 02 '13 at 07:47
  • 6
    It is important to stress that creating a dependency from viewmodel in the view is not the purest mvvm either. When you use `(DataContext as SomeViewModel)`, your view becomes dependent on `SomeViewModel`, which might be undesireable. What do you think? – heltonbiker Apr 20 '15 at 14:29
  • 1
    @MohammadDehghan: How would you implement this using Caliburn? – Niklas Hoesl Nov 29 '15 at 11:59
  • 1
    Very clear answer. Are there any alternatives whilst staying in the MVVM pattern? – rollsch Nov 16 '16 at 05:32
  • 1
    @heltonbiker Why is that undesirable? The view always knows the viewmodel (remember that all bindings refer to viewmodel's properties) and adding one more direct dependency doesn't hurts in any other way. What is "forbidden" is the viewmodel knowing the view directly. – Alejandro Dec 01 '16 at 13:16
  • 1
    @Alejandro I didn't mean it's forbidden, just that it might be undesireable. For example if you have a View in a UserControl library, you might want to use the same view in other projects. I have been using dependency inversion for that lately, so that I have `DataContext as ISomeInterface`, or else `DataContext as SomeAbstractClass` and then I can pass different working implementations. The important thing is to judge if the view is intended for reuse, and if so, avoid coupling it to a specific viewmodel implementation, IMO. – heltonbiker Dec 01 '16 at 14:12
  • 1
    @heltonbiker That's true, but still not a bad practice. It's quite common to have binding of the form `Text={Binding SomeText}`, where `SomeText` is a viewmodel property, meaning the view **already** knows the viewmodel, or at the very least an interface of it. Then casting explictly to a concrete class or an interface is pretty much the same. Casting to one or another depends on your specific case, but the point is, the view knows the viewmodel. – Alejandro Dec 01 '16 at 19:28
  • 1
    @Alejandro Binding creates a very weak coupling, since they are "Stringly Typed". Its the _type_ dependency I think should be avoided if the View is intended for reuse (that is, intended to have different possible viewmodels as datacontext). In you example, usually the View knows only property names, not the _actual type_ of the DataContext, and much less _depends_ on such type. – heltonbiker Dec 02 '16 at 11:35
3

You should move your creation code to View, and ViewModel should just notify view when it should be called.

Lonli-Lokli
  • 3,357
  • 1
  • 24
  • 40
0

You can also use the DataContext (which is the ViewModel) of the View in the code behind of the view, and add the textbox to the grid there. That would make more sense.

If you give the grid a name in your XAML file, you will be able to access the grid in the code behind immediately.

Geerten
  • 1,027
  • 1
  • 9
  • 22
-1

If you are using Caliburn Micro, implement following step:

  1. Make the ViewModel inherited from interface IViewAware; you are going to implement two methods AttachView and GetView of this interface.

  2. Define a variable with type of View to get the reference to the View

  3. See detail below:

    private SomeViewClass v;
    public void AttachView(object view, object context = null)
    {
        v = view as BomView;
        if (ViewAttached != null)
             ViewAttached(this,
             new ViewAttachedEventArgs() { Context = context, View = view });
    }
    
    public object GetView(object context = null)
    {
        return v;
    }
    

Later on you can access a single element on the View through v such as v.txtName="John"; etc...

Wilson Vargas
  • 2,841
  • 1
  • 19
  • 28