0
votes

I have a GeneralView that is a Parent View and when opens it opens the Parent followed by a Child. I want to implement navigation and keep the buttons in the side (like a UserPage). Lets go to the desired behavior and followed by the code I have now.

How I have implemented the ChildView don't change, stays in the HomeView aka FriendsView.

enter image description here

So description Login > GeneralView (that Opens Immediately in the Home) > Click in About and the childView changes to the AboutView, click in home the HomeView is showed again.

What I have:

GeneralView

<UserControl x:Class="WpfWHERE.View.GeneralView"
         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:local="clr-namespace:WpfWHERE.View"
         xmlns:ViewModel="clr-namespace:WpfWHERE.ViewModel"
         mc:Ignorable="d" 
         d:DesignHeight="600" d:DesignWidth="800">
<UserControl.DataContext>
    <ViewModel:GeneralViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
    <DataTemplate DataType="{x:Type ViewModel:FriendsViewModel}">
        <local:FriendsView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type ViewModel:AboutViewModel}">
        <local:AboutView />
    </DataTemplate>
</UserControl.Resources>
<DockPanel Margin="0,0,0,0">
    <StackPanel Orientation="Horizontal">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" MaxWidth="200"/>
            </Grid.ColumnDefinitions>
            <Image Grid.Column="1" x:Name="userImage" Source="/Resources/Images/profileImage.png" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="161" Width="180" />
            <Label Grid.Column="1" x:Name="labelName" Content="NameHere" HorizontalAlignment="Left" Margin="10.4,171,0,0" VerticalAlignment="Top" RenderTransformOrigin="0.536,1.344" Height="26" Width="67"/>
            <StackPanel Grid.Column="1" Margin="0.4,202,-1.2,0" VerticalAlignment="Top" Height="200">
                <MenuItem Style="{DynamicResource MyMenuItem}" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="FriendsViewModel" Header="Home" x:Name="Home"/>
                <MenuItem Style="{DynamicResource MyMenuItem}" Header="Overview" x:Name="Overview"/>
                <MenuItem Style="{DynamicResource MyMenuItem}" Header="Settings" x:Name="Settings"/>
                <MenuItem Style="{DynamicResource MyMenuItem}" Header="About" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="AboutViewModel" x:Name="About"/>
            </StackPanel>
        </Grid>
        <ContentControl Content="{Binding Current_ViewModel}" Height="600" Width="600"/>
    </StackPanel>
</DockPanel>

GeneralViewModel

 class GeneralViewModel:AViewModel
{
    public GeneralViewModel()
    {
        this.AddViewModel(new FriendsViewModel() { DisplayName = "Friends", InternalName = "FriendsViewModel" });
        this.AddViewModel(new AboutViewModel() { DisplayName = "About", InternalName = "AboutViewModel" });
        this.Current_ViewModel = this.GetViewModel("FriendsViewModel");
    }
}

AViewModel Interface

 public abstract class AViewModel : ViewModelBase
{
    public string Name { get; set; }
    public RelayCommand<string> SelectViewCommand { get; set; }

    public AViewModel()
    {
        SelectViewCommand = new RelayCommand<string>(OnSelectViewCommand);
    }

    private static ObservableCollection<ViewModelBase> _ViewModels;
    public static ObservableCollection<ViewModelBase> ViewModels
    {
        get { return _ViewModels; }
        set { _ViewModels = value; }
    }

    public void AddViewModel(ViewModelBase viewmodel)
    {
        if (ViewModels == null)
            ViewModels = new ObservableCollection<ViewModelBase>();

        if (!ViewModels.Contains(viewmodel))
            ViewModels.Add(viewmodel);
    }

    public ViewModelBase GetViewModel(string viewmodel)
    {
        return ViewModels.FirstOrDefault(item => item.InternalName == viewmodel);
    }

    private void OnSelectViewCommand(string obj)
    {
        switch (obj)
        {
            case "ExitCommand":
                Application.Current.Shutdown();
                break;
            default:
                this.Current_ViewModel = this.GetViewModel(obj);
                break;
        }
    }

    private ViewModelBase _Current_ViewModel;
    public ViewModelBase Current_ViewModel
    {
        get { return _Current_ViewModel; }
        set { _Current_ViewModel = value; OnPropertyChanged("Current_ViewModel"); }
    }
}
2
and what is exactly the problem you are facing?sexta13
@sexta13 the view is not changing stays in the FriendsView even when I click the AboutButton. Just o clarify I have two childviews (Friends and About). Friends is loaded at the same time of the general and I want that if I click Home that appears as well. The about should appear when I click about.Antoine
you have to implement INotifyProperty...so that the view knows that something changed.sexta13
Where I have to do that? And calling the event from where?Antoine
As @sexta13 mentioned, you have to implement INotifyPropertyChanged interface. It is the backbone of two-way data binding in WPF. Don't go straight to MVVM without understanding this else you will surely encounter road blocks.jegtugado

2 Answers

1
votes

Try this....

Change this...

        <StackPanel Grid.Column="1" Margin="0.4,202,-1.2,0" VerticalAlignment="Top" Height="200">
            <MenuItem Style="{DynamicResource MyMenuItem}" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="FriendsViewModel" Header="Home" x:Name="Home"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="Overview" x:Name="Overview"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="Settings" x:Name="Settings"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="About" Command="{Binding DataContext.SelectViewCommand, ElementName=GeneralView}" CommandParameter="AboutViewModel" x:Name="About"/>
        </StackPanel>

To this...

        <StackPanel Grid.Column="1" Margin="0.4,202,-1.2,0" VerticalAlignment="Top" Height="200">
            <MenuItem Style="{DynamicResource MyMenuItem}" Command="{Binding SelectViewCommand}" CommandParameter="FriendsViewModel" Header="Home" x:Name="Home"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="Overview" x:Name="Overview"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="Settings" x:Name="Settings"/>
            <MenuItem Style="{DynamicResource MyMenuItem}" Header="About" Command="{Binding SelectViewCommand}" CommandParameter="AboutViewModel" x:Name="About"/>
        </StackPanel>

Note that I have removed 'DataContext' and 'ElementName' from your MenuItems

INotifyProperty is already implemented in ViewModelBase

UPDATE 1

The problem was with ElementName=GeneralView... an Element with that Name does not exist. you could have added x:Name=”GeneralView” to the top of your Base_View XAML BUT there is no need as your ContentControl was bound to Current_ViewModel in the Base_ViewModel anyway....

When you press a button to 'change Views' you are actually changing the value of the property that your ContentControl is bound to, so you have to call the correct SelectViewCommand function in the SAME instance of the class that your ContentControl is bound too....

In the demo you'll see that in the 'LogOn_View' I call

Command="{Binding DataContext.SelectViewCommand, ElementName=Base_V}", CommandParameter="Main_ViewModel"

Here I am calling the SelectViewCommand in the Base_V, That's because I want to change the view that is displayed in the Base_V's ContentControl

In Main_View I call

Command="{Binding SelectViewCommand}", CommandParameter="MainV1_ViewModel"

Here I am calling the SelectViewCommand in the Main_ViewModel, That's because I want to change the View displayed in the ManiView's ContentControl

For anyone that wants the demo code that I am talking about above, you can find it here...

http://www.mediafire.com/download/3bubiq7s6xw7i73/Navigation1.rar

Also, a little update to the code... replace the AddViewModel function in AviewModel with this.....

    public void AddViewModel(ViewModelBase viewmodel)
    {
        if (ViewModels == null)
            ViewModels = new ObservableCollection<ViewModelBase>();

        var currentVNs = (from vms in ViewModels where vms.InternalName == viewmodel.InternalName select vms).FirstOrDefault();
        if (currentVNs == null)
            ViewModels.Add(viewmodel);
    }
1
votes

OK so... After some more thought.... The problem with a lot of tutorial code on the Internet is that it is quite basic. The implementation of the relationship between the Data (Model) <=> View, seems to be a matter of taste. HOWEVER I have only EVER seen it done badly. Personally speaking, most of the WPF applications I have worked on have been absolute rubbish, unmaintainable and unmanageable spaghetti code....You can clearly see that the original developer has 'Learnt' as they went along, everything is everywhere and there is no consistency.... As I normally come on to a project half way though I have no choice other than to continue with the way its been started, so I never gave it much thought, until now!!.

My own understanding is that the 'Model' defines the Data Structure, the 'View' displays that Data to the user and the ViewModel is the bit in-between that translates the Data (Model) into something that can be displayed to the user.

In the ViewModel you would simply have ObservableCollections (Lists of Data (Models)) and single instances of data (a single instance of a Model). The 'View' then Binds to the ObservableCollections (and single Model instances) to display that data (using the magic of XAML and Templating etc.)....

As for passing an object (Class) to the ViewModel, I don't think you would actually need to do that. You would simply create a property in your ViewModel that represents the Data you want to display, then retrieve the Data from a 'Source' as and when needed (typically when the View is displayed but could also be periodicity on a timer\thread or something)....

The main problem with my Demo code (download) was that the constructor of the ViewModels was NEVER being called after the application had started so there was no way of refreshing the Properties, in the ViewModel, from a Data source....

There may very well be a better way to do this BUT I have fixed that problem by introducing an Event called Initialize in the 'ViewModelBase'

    public delegate void MyEventHadler();
    public event MyEventHadler Initialize;

    public  void InitializeFunction()
    {
        if (Initialize != null)
            Initialize.Invoke();
    }

that Event can then be subscribed to in the constructor of each ViewModel

    public MainV2_ViewModel()
    {
        this.Initialize += MainV2_ViewModel_Initialize; // our new Event
    }

And the Event Stub that is called when we navigate to this ViewModel\View from somewhere else....

    private void MainV2_ViewModel_Initialize()
    {
        // So here we are retrieving a List of All users from the WCF Service
        this.AllUsers = new ObservableCollection<ServiceReference1.User>( DataAccessLevel.sr1.GetAllUsers());
        // Now our AllUsers property has been updated and the View will display the new data
    }

Now when you switch from one ViewModel\View to another, the Initialize Event is called\raised in AviewModel\Current_ViewModel Property Setter

    private ViewModelBase _Current_ViewModel;
    public ViewModelBase Current_ViewModel
    {
        get { return _Current_ViewModel; }
        set {
            _Current_ViewModel = value;
            // the Constructor of the ViewModel never gets called more that once on App Start 
            // so we have to implement/raise our own event when changing from one View to another.
            if (Current_ViewModel != null)
                Current_ViewModel.InitializeFunction(); // InitializeFunction will fire the event in this ViewModel, we can now initialise the properties.
            OnPropertyChanged("Current_ViewModel"); }
    }

this will then fire the 'Initialize' Event in the ViewModel that is being Switched too, giving us the opportunity to refresh the Data.....

The Demo Code is now a fully functioning application (still needs work of course). It provides the following functionality...

Register a New User :: Create a new user with UserName and Password

Log a User On :: Log on existing User with UserName and Password

Recover a User (forgotten password) :: Reset the Password of a registered User (using existing UserName and new Password)

In addition, error messages are returned from the WCF Service that are then displayed in an Error View (See LogOnError_ViewModel and LogOnError_View) when something goes wrong (incorrect Password etc.)

find the Demo Code here....

http://www.mediafire.com/download/881yo6reo55tm8l/Navigation1_WCF_EF_05052016.rar

The Demo Code comes with a WCF Service and a WPF application, because it uses a WCF Service it can only be run from the Visual Studio IDE (Unless you deploy the WCF service to IIS). The WCF service uses Entity Framework and (should) create\attach a database to store User Data when you first register a new user...

Maybe you can improve on the code or get some ideas.....