Domanda

http://i.stack.imgur.com/v58ov.png

See the picture above. This is a screenshot from Visual Studio's options form.

The left side is essentially a TreeView. The right side is various controls that change program options. When nodes in the TreeView are selected, the right side changes, showing different options.

How do you program something like this? Does the right side just have 50 overlapping panels, and the selecting of nodes just changes which panel is visible? If this is the case, how would you go about managing such? It would be a mess in the designer.

È stato utile?

Soluzione

No you don't make 50 overlapping panels. Just create several usercontrols and, for example, link the types on the tag of a node. You can use the Activator to create the controls. Create 1 treeview and 1 panel: (PSEUDO CODE)

// create nodes:
TreeNode item = new TreeNode();

item.Tag = typeof(UserControl1);

TreeView.Nodes.Add( item );


// field currentControl
UserControl _currentControl;


// on selection:
TreeViewItem item = (TreeViewItem)sender;

if(_currentControl != null)
{
   _currentControl.Controls.Remove(_currentControl);
   _currentControl.Dispose();
}

// if no type is bound to the node, just leave the panel empty
if (item.Tag == null)
  return;

_currentControl = (UserControl)Activator.Create((Type)item.Tag);
Panel1.Controls.Add(_currentControl);

The next question would be, "I'd like to call a save method, or RequestClose method in the controls". For this, you should implement an Interface on the controls, and when you switch nodes, just try to cast the _currentusercontrol to IRequestClose interface and call, for example, bool RequestClose(); method.

 // on selection:
 TreeViewItem item = (TreeViewItem)sender;

 if(_currentControl != null)
 {
    // if the _currentControl supports the IRequestClose interface:
    if(_currentControl is IRequestClose)
        // cast the _currentControl to IRequestCode and call the RequestClose method.
        if(!((IRequestClose)_currentControl).RequestClose())
             // now the usercontrol decides whether the control is closed/disposed or not.
             return;

    _currentControl.Controls.Remove(_currentControl);
    _currentControl.Dispose();
 }

 if (item.Tag == null)
   return;

_currentControl = (UserControl)Activator.Create(item.Tag);
Panel1.Controls.Add(_currentControl);

But this will be the next step.

Altri suggerimenti

For me, the common design of that is, a classical treeview on the left side and a "content zone" on the right side. When the user pick something in the treeview you load the related view in the content zone. After there's a lot of different way to implement the stuff, for example automaticaly generate the treeview based on a list of object which contain the type of view to be instanciated and create a generic instantiator called when an item is picked to create the related view, anyway, the background is still the same. To resume, a treeview and just create the view in the content zone based on the selected item. (I've seen several screen like that in my work and most of the time it was like that)

My approach, after checking several options, was to inherit the TabControl component in such a way the pages of the control can be used as paged panels, and adding functionality so that the tabs do not show at run time. Then, by creating a property called Pages which depends on TabPages, I can refer to each page in a semantically correct way, giving the advantage of being able to manage every page as part of the Pages collection, and also hierarchically through the document explorer.

The code also hides design-time properties that pertain to a regular TabControl, but that would be irrelevant in a paged panel. Below is the code if anyone is interested.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;

namespace MyCustomControls
{
    public class PagedPanel : TabControl
    {
        //------------------------------------------------------------------------------------------------
        public PagedPanel()
        {
            base.Multiline = true;
            base.Appearance = TabAppearance.Buttons;
            base.ItemSize = new Size(0, 1);
            base.SizeMode = TabSizeMode.Fixed;
            base.TabStop = false;
        }
        //------------------------------------------------------------------------------------------------
        protected override void WndProc(ref Message m)
        {
            // Hide tabs by trapping the TCM_ADJUSTRECT message
            if (m.Msg == 0x1328 && !DesignMode) m.Result = (IntPtr)1;
            else base.WndProc(ref m);
        }
        //------------------------------------------------------------------------------------------------
        protected override void OnKeyDown(KeyEventArgs ke)
        {
            // Block Ctrl+Tab and Ctrl+Shift+Tab hotkeys
            if (ke.Control && ke.KeyCode == Keys.Tab)
                return;
            base.OnKeyDown(ke);
        }
        //------------------------------------------------------------------------------------------------
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [DefaultValue(true)]
        public new bool Multiline
        {
            get { return base.Multiline; }
            set { base.Multiline = value; Invalidate(); }
        }
        //------------------------------------------------------------------------------------------------
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
        , DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [DefaultValue(TabAppearance.Buttons)]
        public new TabAppearance Appearance
        {
            get { return base.Appearance; }
            set { base.Appearance = value; Invalidate(); }
        }
        //------------------------------------------------------------------------------------------------
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
        , DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [DefaultValue(typeof(Size), "0, 1")]
        public new Size ItemSize
        {
            get { return base.ItemSize; }
            set { base.ItemSize = value; Invalidate(); }
        }
        //------------------------------------------------------------------------------------------------
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
        , DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [DefaultValue(TabSizeMode.Fixed)]
        public new TabSizeMode SizeMode
        {
            get { return base.SizeMode; }
            set { base.SizeMode = value; Invalidate(); }
        }
        //------------------------------------------------------------------------------------------------
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)
        , DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new TabPageCollection TabPages
        {
            get { return base.TabPages; }
        }
        //------------------------------------------------------------------------------------------------
        [EditorBrowsable(EditorBrowsableState.Never), Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [DefaultValue(false)]
        public new bool TabStop
        {
            get { return base.TabStop; }
            set { base.TabStop = value; Invalidate(); }
        }
        //------------------------------------------------------------------------------------------------
        public TabPageCollection Pages
        {
            get { return base.TabPages; }
        }
        //------------------------------------------------------------------------------------------------
    }
}

The treeview would handle calling each tab either by key or index, a relatively trivial task. I do this by naming the nodes in my tree with a prefix such as "tvn", and then naming the pages in the PagedPanel the same but with prefix "pg". So on the AfterSelect event of the treeview, all I need is the name of the current node and I know what page to show.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top