WPF Window hide Content DependencyProperty

418 Views Asked by At

I want to create a Window which redeclares it's own DependencyProperty named Content.

public partial class InfoWindow : Window
{
    public static new readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(InfoWindow), new PropertyMetadata(null));
    public object Content
    {
        get { return GetValue(ContentProperty); }
        set { SetValue(ContentProperty, value); }
    }
}

And XAML bind this property

<ContentControl Content="{Binding ElementName=_this, Path=Content}" />

It works fine, just the Visual Studio Designer complains Logical tree depth exceeded while traversing the tree. This could indicate a cycle in the tree.

Is there any way how to tell the Designer that binding is to the InfoWindow.Content and not Window.Content? Or is it a bad idea hide the property and should I renamed my property?

1

There are 1 best solutions below

1
On

What I am trying to achieve here is the idea of dynamically defining the Buttons that are used to bring up different views for navigating to different forms. (See below: ) The link between the View and View Models are setup inside the Dictionary View_ViewModel which is used to identify the view to set for the Current view when the button is pressed. (Note: I have tried to use the most basic objects avoiding IOC containers and such like, so as to make it easier to understand the code) The most important thing to remember is to set the DataContext correctly otherwise you will get the Logical tree depth exceeded while traversing the tree. Error. You could either do this in the Code behind of the View or inside the XAML. Example:

public partial class SetupForm : UserControl
{
    public SetupForm()
    {
        InitializeComponent();
        DataContext = new SetupFormVM();
    }
}

OR

<UserControl.DataContext>
    <SaleVM:SalesEntryVM />
</UserControl.DataContext>

Here is a code snippet which probably explains it more clearly and probably answers your question. The view model defines the how many buttons and views you want in the Main Window. This is achieved by having ItemsControl binding to a list in View Model class. The Button command is bounded to ICommand ChangeViewCommand property in the View Model class which evaluates the Button pressed and .calls ViewChange method which changes the CurrentView (this is bound to the Content in the XAML)

This is what my main Window xaml looks like.

<Window x:Class="MyNameSpace.Views.ApplicationWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:MyNameSpace.Views"
    mc:Ignorable="d"
    Title="ApplicationWindow" Height="Auto" Width="Auto">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition /> -------------------> repeated five times
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition /> -------------------------> repeated five times
    </Grid.RowDefinitions>
    <DockPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" >
        <!-- Bind to List of Pages -->
        <ItemsControl ItemsSource="{Binding ControlItemsNamesList}" DockPanel.Dock="Top" >
            <!-- Stack the buttons horizontally --> The list contains the labels to assign to the buttons
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>            ---------------------------------------> This to stack the buttons Horizontally
                    <StackPanel Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <!-- This looks at the list items and creates a button with ControlName -->
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Content="{Binding ControlName}"
                            Command="{Binding DataContext.ChangeViewCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"  ------> This is important for the Buttons to work Window or ContentControl.
                            CommandParameter="{Binding }"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </DockPanel>
    <ContentControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="5" Grid.RowSpan="4"  Content="{Binding CurrentView}"/> ---------> This is where I want the new Windows to appear when I click the button
</Grid>

This is what one of my User control xaml looks like that will appear when I click the button in the Main Window.

<UserControl x:Class="MyNameSpace.Views.SetupForm"
        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" 
        mc:Ignorable="d" 
        d:DesignHeight="450" d:DesignWidth="800">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition /> ------------------- repeated five times
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition/> ------------------- repeated five times
    </Grid.RowDefinitions>
    <DockPanel Grid.Row="0" Grid.Column="0" Grid.RowSpan="5" Background="AliceBlue" Margin="0,0,0,0" >
        <!-- Bind to List of Pages -->
        <ItemsControl ItemsSource="{Binding ControlItemsNamesList}" DockPanel.Dock="Left" >
            <!-- Stack the buttons horizontally -->
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Vertical" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <!-- This looks at the list items and creates a button with ControlName -->
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Content="{Binding ControlName}"
                            Command="{Binding DataContext.ChangeViewCommand, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}"
                            CommandParameter="{Binding }"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </DockPanel>
    <ContentControl Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="5" Grid.RowSpan="4"  Content="{Binding CurrentView}"/>
</Grid>
</UserControl>

This is the ViewModel for the Main window:

using Products.MVVMLibrary;
using Products.MVVMLibrary.Interfaces;
using MyNameSpace.Views;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Input;

namespace MyNameSpace.ViewModel
{
public class ApplicationVM : ObservableObject
{
    private MyNameSpace IControlItem currentNavigationItem;
    private MyNameSpace ContentControl currentView;

    private MyNameSpace List<IControlItem> NavigationList;

    private MyNameSpace ICommand changeViewCommand;
    private MyNameSpace ViewConverter viewDictionary;
    private MyNameSpace Dictionary<string, LinkViewToViewModel> View_ViewModel;
    public ApplicationVM()
    {
        viewDictionary = new ViewConverter();
        View_ViewModel = new Dictionary<string, LinkViewToViewModel>();
        NavigationList = new List<IControlItem>();
        InitialiseLists();
    }

    private MyNameSpace void AddControlNavigationItems(string name, ContentControl view, ObservableObject viewModel)
    {
        View_ViewModel.Add(name, new LinkViewToViewModel(view, viewModel));
        IControlItem item = (IControlItem)viewModel;
        NavigationList.Add(item);

    }
    private MyNameSpace void InitialiseLists()
    {
        AddControlNavigationItems("Sales", new SalesForm(), new SalesEntryVM());
        AddControlNavigationItems("Purchases", new PurchaseEntryForm(), new PurchasesVM());
        AddControlNavigationItems("Setup", new SetupForm(), new SetupFormVM());

        //Use the property instead which creates the instance and triggers property change
        CurrentViewModel = (IControlItem)View_ViewModel[View_ViewModel.Keys.ElementAt(0)].ViewModel;
        CurrentView = View_ViewModel[View_ViewModel.Keys.ElementAt(0)].View;
    }

    public List<IControlItem> ControlItemsNamesList
    {
        get => NavigationList;
    }
    /// <summary>
    /// Provides a list of names for Navigation controls to the control item
    /// </summary>
    public Dictionary<string, LinkViewToViewModel> ApplicationViews
    {
        get
        {

            return View_ViewModel;
        }
        set
        {
            View_ViewModel = value;
        }
    }

    public ContentControl CurrentView
    {
        get
        {
            return currentView;
        }
        set
        {
            currentView = value;
            OnPropertyChanged("CurrentView");
        }
    }
    public IControlItem CurrentViewModel
    {
        get
        {
            return currentNavigationItem;
        }
        set
        {
            if (currentNavigationItem != value)
            {
                currentNavigationItem = value;
                OnPropertyChanged("CurrentViewModel");
            }
        }
    }
    /// <summary>
    /// This property is bound to Button Command in XAML.
    /// Calls ChangeViewModel which sets the CurrentViewModel
    /// </summary>
    public ICommand ChangeViewCommand
    {
        get
        {
            if (changeViewCommand == null)
            {
                changeViewCommand = new ButtonClick(
                    p => ViewChange((IControlItem)p), CanExecute);
            }
            return changeViewCommand;
        }
    }

    #region Methods

    private MyNameSpace void ViewChange(IControlItem viewname)
    {
        foreach (KeyValuePair<string, LinkViewToViewModel> item in View_ViewModel)
        {
            if (item.Key == viewname.ControlName)
            {//Set the properties of View and ViewModel so they fire PropertyChange event
                CurrentViewModel = (IControlItem)item.Value.ViewModel;
                CurrentView = item.Value.View;
                break;
            }
        }

    }
    private MyNameSpace bool CanExecute()
    {
        return true;
    }
    #endregion
  }
}

Other classes

public class LinkViewToViewModel
{
    public LinkViewToViewModel(ContentControl view, ObservableObject viewModel)
    {
        View = view;
        ViewModel = viewModel;
    }
    public ContentControl View { get; set; }
    public ObservableObject ViewModel { get; set; }
}