views:

619

answers:

1

Hello,

In my scenario I have a MainView + MainViewModel, UserControl1 + UserControl 2. In the MainView I have 2 buttons labeled: Button_ShowUserControl1 + Button_ShowUserControl2. At the lower part of the MainView I have a "ContentGrid" which takes/should_take... every UserControl.

My goal:

When Button_ShowUserControl1 is clicked UserControl1 is Visible and UserControl2 OR any other UserControl must be set to Collapsed. Same is valid for Button_ShowUserControl2.

My problem:

1.) As the UserControls shall be loaded at application start how can I put them all together in the one "ContentGrid"? Thats actually not possible... so how can I make one UserControl visible while the other is in the same place/"ContentGrid" just collapsed ?

2.) As 1.) seems not possible how can I instantiate all UserControls at start of the application and make them only Visible/Collapsed when respective Button is clicked?

3.) As a UserControl has a property Visibility = Visible/Hidden/Collapsed, how can I bind to a property in a ViewModel return such a value like Collapsed? I only could get a boolean value like Visibility = false/true ?

My testcode:

<Grid x:Name="LayoutRoot" Background="#FFBDF5BD" ShowGridLines="False">
    <Grid.RowDefinitions>
        <RowDefinition Height="96*" />
        <RowDefinition Height="289*" />
    </Grid.RowDefinitions>      
    <Grid HorizontalAlignment="Stretch" Name="MenuGrid" VerticalAlignment="Stretch" Background="#FFCECEFF">
        <StackPanel Name="stackPanel1" Background="#FFEDFF00" Orientation="Horizontal">
            <Button Content="User Data 1" Height="35" Name="button1" Command="{Binding  Path=ShowUserControl1Command}" Width="150" Margin="100,0,0,0" />
            <Button Content="User Data 2" Height="35" Name="button2" Width="150" Margin="100,0,0,0" />
        </StackPanel>
    </Grid>
    <Grid Grid.Row="1" HorizontalAlignment="Stretch" Name="ContentGrid" VerticalAlignment="Stretch" Background="#FFB15454" />
</Grid>

<UserControl x:Class="SwapUserControls.MVVM.UserControl2"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:vm="clr-namespace:SwapUserControls.MVVM.ViewModel"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300" Visibility="{Binding IsUserControl1Collapsed, Path=Value}">

<UserControl.Resources>
    <vm:MainViewModel x:Key="MainViewModelID" />
</UserControl.Resources>

<UserControl.DataContext>
    <Binding Source="{StaticResource MainViewModelID}" />
</UserControl.DataContext>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="228*" />
        <RowDefinition Height="72*" />
    </Grid.RowDefinitions>
    <Button Content="UserControl2" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="112,27,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
    <DataGrid HorizontalAlignment="Stretch" Name="dataGrid1" VerticalAlignment="Stretch" Background="#FFC046F8" />
</Grid>

public class MainViewModel : ViewModelBase
{
    RelayCommand _ShowUserControl1Command;
    private bool _IsUserControl1Collapsed;

    public RelayCommand ShowUserControl1Command
    {
        get
        {
            if (_ShowUserControl1Command == null)
            {
                _ShowUserControl1Command = new RelayCommand( () => ShowUserControl1() );                       
            }
            return _ShowUserControl1Command;
        }
    }

    public void ShowUserControl1()
    {
        _IsUserControl1Collapsed = true;
    }

    public bool IsUserControl1Collapsed 
    {          
        get
        {
            return _IsUserControl1Collapsed;
        }  
    }        
}

Yes the code is wrong, therefore I ask here :)

+4  A: 

You only have 2 things wrong with this code.

1) You can't set the visibility of a usercontrol directly... you have to set it on a container:

<Grid Visibility="Collapsed">
    <myControls:MyUserControl />
</Grid>

2) Visibility is not a boolean value, it is an enum. As such, you will need to use a converter to convert from boolean to Visibility. Observe:

<Window ...>
<Window.Resources>
     <BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>

<Grid Visibility="{Binding ShouldShowUsercontrol1, Converter={StaticResource BoolToVis}}">
     <myControls:MyUserControl />
</Grid>
</Window>

That should be it. Hope this helps.

There are other things that you are leaving clues about that might affect the ability of this to work. For example, you don't show the biggest container element... are you wrapping everything in a StackPanel? If you are wrapping everything in a Grid, for example, the controls will overlay everything in layers.

Try these changes I suggest... it should get you closer.


Edit: Another idea using data templates

Another thing you could do is make sure you have a unique ViewModel for each of these views you want to show and hide:

public class MyFirstViewModel : ViewModel
{

}

public class MySecondViewModel : ViewModel
{

}

Then from your "parent" or "main" ViewModel, you show or hide the views you want by virtue of having them in a collection of ViewModels:

public MyMainViewModel : ViewModel
{
     public ObservableCollection<ViewModel> ViewsToShow
     {
          ...
     }

     public void ShowFirstViewModel()
     {
          ViewsToShow.Add(new MyFirstViewModel());
     }
}

To wire everything up in your view, you would then datatemplate these types with their user controls (but this would not cause those views to be instantiated unless they were needed:

<Window ...>
     <Window.Resources>
          <DataTemplate DataType="{x:Type myViewModels:MyFirstViewModel}">
               <myViews:MyFirstView />
          </DataTemplate>

          <DataTemplate DataType="{x:Type myViewModels:MySecondViewModel}">
               <myViews:MySecondView />
          </DataTemplate>
     </Window.Resources>

     <ItemsControl ItemsSource="{Binding ViewsToShow}" />

</Window>

And for any ViewModels you put in "ViewsToShow", the view will automatically see that and template in the appropriate view. Again, without instantiating it before it's needed.

This is probably a little cleaner than putting everything single thing in the View and setting visibility, but it would be dependent on you have a unique view model type for every view, which might not be the case.


The question of saving state comes up when using the DataTemplated approach. The solution here is to tread your ViewModel as the state of the control and design both your ViewModels and your Views accordingly. Here is an example that allows you to swap out your Views using DataTemplating, but switching back and forth saves state.

Assume you have the setup from the last section w/ 2 viewmodels that have datatemplates defined. Let's change up the MainViewModel a little:

public MyMainViewModel : ViewModel
{
     public RelayCommand SwapViewsCommand
     {
          ...
     }

     public ViewModel View
     {
          ...
     }
     private ViewModel _hiddenView;
     public MyMainViewModel()
     {
          View = new MyFirstViewModel();
          _hiddenView = new MySecondViewModel();
          SwapViewsCommand = new RelayCommand(SwapViewModels);
     }

     public void SwapViewModels()
     {
          var hidden = _hiddenView;
          _hiddenView = View;
          View = hidden;
     }
}

And a few changes to the main view. I've omitted the DataTemplates for brevity.

<Window ...>
     <!-- DataTemplates Here -->
     <Button Command="{Binding SwapViewsCommand}">Swap!</Button>
     <ContentControl Content="{Binding View}" />
</Window>

That's it. The secret here is I'm saving the reference to the original view model. This way, let's say there is a string property in a viewmodel and an associated textbox in the DataTemplated usercontrol with a two-way binding then the state will essentially be saved.

Anderson Imes
Hello Anderson,I did what you said nearly...: only 500 chars left so => http://pastebin.com/m35c9bbbaI have just used a button to toggle a usercontrol just for test purposes wether your suggestion works.My problem now is how can I put 8 UserControls into one and the same grid to collapse/visible them by pressing one of the 8 buttons at the top?Should I put the all in a StackPanel, set them all to visibility=collapsed at application start except UserControl1 and when I click UC1 I make every other ...no that cant work. Every UC needs its own Grid to make it visible/collapsed? confused!
msfanboy
@msfanboy it looks like you understand the situation correctly.
Anderson Imes
Do you see a solution when I would use a List<UserControl> or ObservableCollection<UserControl> in my ViewModel where I would load all 8 UserControl uc1,uc2,uc3 etc... instances into that collection. The ContentGrid.Children property would then be bound to this collection. Depending on what button is pressed at the menu btn1, btn2, btn3 etc... only a certain element would be visible via binding to ViewModel property and the rest of the elements would be collapsed :) good idea or bad idea?Hm but then still the UserControls need everyone its own Grid...any Idea Mr. Anderson? ;P
msfanboy
@msfanboy Sure you could do that pretty easily (ItemsControl + this would do it), however that is really sort of blurring the lines between View and ViewModel... I wouldn't recommend it. One option in WPF (not Silverlight is my understanding) is that you can have a collection of ViewModels in your ViewModel that are datatemplated with the right view. I'll edit my sample with this example.
Anderson Imes
quote:"...would be dependent on you have a unique view model type for every view, which might not be the case...."well I have actually total different ViewModels later when I implement them. In the one VM I have customer entities in the other VM I have lets say products/order stuff. So am I right that I can not use then your suggestion?
msfanboy
@msfanboy: If you have a one to one relationship between View types and ViewModel types, you can use it, otherwise you are back to declaring every possible view on the page and showing or hiding. That's pretty much the best you are going to get without violating quite a bit of MVVM by storing the Views themselves in a collection on your main ViewModel.
Anderson Imes
@msfanboy: It's possible it will work... I'm not really sure what you mean with the phrase "I have actually totally different viewModels later when I implement them". I'd suggest you take my sample code and put together a test project that does what I'm suggestion so you know exactly what I mean and what templating does for you. There are very few situations that you couldn't refactor to fit this model.
Anderson Imes
"...If you have a one to one relationship between View types and ViewModel types, you can use it, ..."@Anderson: actually I should have a 1:1 relation and actually isn`t MVVM to be supposed that a View has exactly one ViewModel? like in MVP..."...I'd suggest you take my sample code and put together a test project that does what I'm suggestion so you know exactly what I mean and what templating does for you..."ok I will do now and post here :)
msfanboy
ok man I had just reinstalled my windows because I forgot since yesterday my visual studio 2010 beta 2 is trashed. It always hang up loading wpf projects... as I must do extra hours for my job next days I will return here midst of next week - damn this I already told another guy in another question here on stackoverflow ;P -I have also found this maybe it interests you too:http://www.japf.fr/2009/03/thinking-with-mvvm-data-templates-contentcontrol/See you next week and happy coding :)
msfanboy
Again me I saw something confusing please correct it:public MyFirstViewModel : ViewModel{}shouldn`t it be calledpublic class MyFirstViewModel{} ? maybe a typo don`t know... and why do you inherit from ViewModel? That type does nowhere exist. How does this type look?
msfanboy
With DataTemplates the single pages would be loaded again and again everytime I click on them... I just want to make the collapse...
msfanboy
@msfanboy: you have to decide what is best for you. Create every possible control and show or hide or just create them when they need to be shown. I'm sorry, but there is very little compromise here without compromising mvvm. You are right that those types should have "class" in them. I think you have everything you need now, you just need to make a decision as to your approach.
Anderson Imes
Thank you Anderson for everything you was of a great help to me. I will have to make a decision! :)
msfanboy
and my decision is definitively not using datatemplates. When I switch... the content all former selection in comboboxes/listboxes/grid/checkbox/radiobutton etc are gone ??? how stupid is that... so I will try this way: "...otherwise you are back to declaring every possible view on the page and showing or hiding. That's pretty much the best you are going to get without violating quite a bit of MVVM by storing the Views themselves in a collection on your main ViewModel. – Anderson Imes..." :)
msfanboy
@msfanboy: you won't lose the data if you store the instance of the viewmodel, rather than replacing it with a new one. When you remove a viewmodel, just put that instance in a holding container and when you need to re-show the viewmodel, put that instance back in the collection, rather than instantiating a new one. This will maintain the state.
Anderson Imes
so the controls selection state like first row selected in datagrid will be saved? That would be fine, I will try later and report you hehe
msfanboy
ah maybe you misunderstood me, I thought you talked about datatemplates and keeping the view state not?
msfanboy
@msfanboy - I understood you correctly. Save the ViewModel backing the DataTemplate and it will save the state.
Anderson Imes
sorry for late reply I did not get any notice when I checked my account but funnily here is an answer from you...oh I forgot much hope I can keept up with the stuff you told me now haha, how would you save the ViewModel/visual state using datatemplates ? Any technical advice on that? The SelectemItem normally + datatemplate determine what is to be shown. How should I change that to save the visual state?
msfanboy
ok I have not read your answer from => Jan 25 at 2:11Code: http://pastebin.com/m474c5b7b The ContentControl in my xaml is bound to the ElementName="MyListBox" + the SelectedItem=Settings property in my ViewModel. When the ListItem is changed in the ListBox driving the DataTemplates/UserControls display how could I interfere in this process to set my former created ViewModel again as the current/selected ViewModel?
msfanboy
this thread: http://stackoverflow.com/questions/2080764/wpf-tabcontrol-how-to-preserve-control-state-within-tab-items-mvvm-patternsays the solution is in the WAF app/writer. I checked it and this framework does not use datatemplates to save the visual state. Its just binding objects not more... Anderson you wrote in that thread too :Pso what was your solution?
msfanboy
@msfanboy If you reread it you will note that the problem the OP had was that he couldn't save the state of a visual *unless* he had a ViewModel backing the template. You have to save the very same object you had available previously. I can see you are struggling with this so I will add another sample to this answer that only deals with 2 view models. I hope this is enough.
Anderson Imes
haha I had the solution all the time. I just did not test the textbox when I did some viewmodel instance changes...thats all you need to be happy, no 2way thingie orhidden confusing stuff ;-)http://pastebin.com/m52196dabthank you again for all your help and advise!
msfanboy
I was wrong! The cause that it seemed to work was the transitionals framework which saved the visual state fading back/forth the datatemplate...
msfanboy
The code with the hidden viewmodel can not work with more than 2 ViewModels... how you know which VM got switched having 10 different of them? so which is the first you assign to _hiddenView = new MySecondViewModel(); the ThirdViewModel? 4thViewModel ?
msfanboy
@msfanboy sorry man. This really isn't that complicated. You'll have to keep a collection of all of them. I really can't keep sending you the code for this... you have everything you need to complete this. I have faith you will figure it out.
Anderson Imes
- I have a string Property in my ViewModel- My Textbox binds to this Property with Mode=TwoWay- My Textbox is in the DataTemplated UserControlThe string I entered in the TextBox was lost after switching forward/backward.Only the magic _hiddenview stuff is missing.Yes its actually easy but as I said before how can your swap example work with more than 2 ViewModels datatemplated ?Lets assume I am switching 10 ViewModels and use only one ViewModel _hiddenview how can I save 10 Visual states in ONE _hiddenView ? Thats not logical.
msfanboy
@msfanboy that was just the sample. You'll have to store them in a COLLECTION if you want to do more than 2.
Anderson Imes
endless times I have gone trough that idea using a collection of course having 10 VM`s to backup. When you press button 1 for VM1 from where you know where VM1 is in the collection ? index 1 ? thats stupid not pure at all and very risky if you develop more ViewModels or remove you must keep in sync the index...
msfanboy
@msfanboy that's a domain specific strategy you'll have to come up with yourself. If you have very few views, tracking indexes might be the way to go. If you have many, another strategy is likely appropriate. I don't think I have enough information to really help you there. Good luck.
Anderson Imes