How to create an options form in C# Windows Forms? - c#

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.

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.

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.

Related

Attached Property for Binding to WebBrowser not working

I have been looking for a way to get the HTML out of a WPF WebBrowser control. The two best options I have found are to bind a customer attached property to the property in the application or to build a new control from the WebBrowser control. Considering my level of knowledge and the fact that (as of now I really only need this one time) I chose the first. I even considered breaking MVVM style and using code-behind but I decided not to give up in the binding.
I found several examples on creating the attached property, I finally chose this one, from here Here:
namespace CumminsInvoiceTool.HelperClasses
{
public static class WebBrowserExtentions
{
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.RegisterAttached("Document", typeof(string), typeof(WebBrowserExtentions), new UIPropertyMetadata(null, DocumentPropertyChanged));
public static string GetDocument(DependencyObject element)
{
return (string)element.GetValue(DocumentProperty);
}
public static void SetDocument(DependencyObject element, string value)
{
element.SetValue(DocumentProperty, value);
}
public static void DocumentPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
WebBrowser browser = target as WebBrowser;
if (browser != null)
{
string document = e.NewValue as string;
browser.NavigateToString(document);
}
}
}
}
I also added the following to the xaml for the WebBrowser control (I have tried both with and without the "Path=" in the xaml:
<WebBrowser local:WebBrowserExtentions.Document="{Binding Path=PageCode}" Source="https://www.cummins-distributors.com/"/>
My View has a tab control one tab has the WebBrowser control and another tab has a textbox. When I click the get code the viewModel runs a function to set property bound to the textbox to the string the attached property of the WebBrowser is bound to. Below is the code of my ViewModel.
namespace CumminsInvoiceTool.ViewModels
{
class ShellViewModel : Screen
{
private string _browserContent;
public string BrowserContent
{
get { return _browserContent; }
set {
_browserContent = value;
NotifyOfPropertyChange(() => BrowserContent);
}
}
private string _pageCode;
public string PageCode
{
get { return _pageCode; }
set {
_pageCode = value;
NotifyOfPropertyChange(() => PageCode);
}
}
public void StartProgressCommand()
{
}
public void GetContent()
{
if (!string.IsNullOrEmpty(PageCode))
{
BrowserContent = PageCode;
}
else
{
MessageBox.Show("There is no cintent to show", "No content Error", MessageBoxButton.OK);
}
}
}
}
The application compiles and runs but when I click "Get Code" I am getting the messagebox for "PageCode" is empty.
When I set a break point at the beginning of the function for the button, the PageCode string is showing "null".
Is this an issue because I am using Caliburn.Micro or am I missing something else?
------- EDIT for comments ----------
The button calls GetContent() in the "ShellViewModel" code above. I know the button is bound and working because the app is showing the custom messagebox I have set up to let me know when "pageCode" is null or empty.
The textbox looks like:
<TextBox x:Name="BrowserContent"/>

Adding controls to a panel on a User Control in designer

I have a specific requirement to create a user control with specific common functions. To that control I also have the requirement to allow other developers to add controls in designer mode to make specific UI's. To do this I created a user control, adding (sample) label, and button. I also added a panel to allow adding of addition controls in a specific area of the control.
I then made the made the class visible in designer mode by adding [Designer] markup and a [ControlDesigner]. This gives the desired effect to add a User control with some fixed content, and add more controls to the page. The problem is that the panel can be moved by the user in design mode, and VisualStudio gets confused, creating a circular reference.. I must be missing something? Can I turn off the resizing/positioning of the panel, even though I need design mode enabled?
NOTE: I also tried to just use a user control in design mode, but added controls keep disappearing behind the fixed controls on the User Control.
Code and examples are below.. Any suggestion/fixes welcomed..
Above is the visual of the user control with the panel
Above is a form including the User control, and adding a custom button to the panel.. Note the panel drag is enable, if touched, a circular reference gets created in the form.designer.cs file, and the project becomes unstable.
Finally below is the class for User Control
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.Windows.Forms.Design;
namespace wfcLib
{
[DesignerAttribute(typeof(MyControlDesigner))]
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
public partial class ucInput : UserControl
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Panel InternalPanel
{
get { return pnlContent; }
set { pnlContent = value; }
}
public ucInput()
{
InitializeComponent();
}
}
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
public class MyControlDesigner : System.Windows.Forms.Design.ControlDesigner
{
public override void Initialize(IComponent c)
{
base.Initialize(c);
ucInput ctl = (ucInput)c;
EnableDesignMode(ctl.InternalPanel, "InternalPanel");
}
}
}
In addition to my comment concerning using a derived Panel with its own designer that overrides the SelectionRules property, another method would be to tap into the designer's ISelectionService to detect a change in selected components and remove the panel if it was selected.
This is accomplished by overriding the control's Site property to set the hook. Also note that I changed the InternalPanel property to be read-only as you really do not want that writable.
[DesignerAttribute(typeof(MyControlDesigner))]
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
public partial class ucInput : UserControl
{
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Panel InternalPanel
{
get { return pnlContent; }
}
public ucInput()
{
InitializeComponent();
}
private ISelectionService selectionService;
private IDesignerHost host;
public override ISite Site
{
get
{
return base.Site;
}
set
{
host = null;
UnSubscribeFromSelectionService();
base.Site = value;
if (value != null)
{
host = (IDesignerHost)this.Site.GetService(typeof(IDesignerHost));
if (host != null)
{
if (host.Loading)
{
// defer subscription to selection service until fully loaded
host.Activated += Host_Activated;
}
else
{
SubscribeToSelectionService();
}
}
}
}
}
private void Host_Activated(object sender, EventArgs e)
{
host.Activated -= Host_Activated;
SubscribeToSelectionService();
}
private void SubscribeToSelectionService()
{
selectionService = (ISelectionService)this.Site.GetService(typeof(ISelectionService));
if (selectionService != null)
{
selectionService.SelectionChanging += OnSelectionChanging;
}
}
private void UnSubscribeFromSelectionService()
{
if (selectionService != null)
{
selectionService.SelectionChanging -= OnSelectionChanging;
}
}
private void OnSelectionChanging(object sender, EventArgs e)
{
if (selectionService.GetComponentSelected(pnlContent))
{
selectionService.SelectionChanging -= OnSelectionChanging;
selectionService.SetSelectedComponents(new[] { pnlContent }, SelectionTypes.Remove);
selectionService.SelectionChanging += OnSelectionChanging;
}
}
}
Edit: The original code neglected to account for SelectionService not being available while the IDesignerHost is loading. Added code to defer subscription until the IDesignerHost is activated.

C#: Create a Custom Control textbox which triggers events

I'm making a custom control textbox that has a Cue (filler text) and CueColor (filler text color) properties. I created an Enter and Leave event inside the textbox to regulate the Cue. When I tried applying it, however, it crashes my IDE (Visual Studio 2015, if this helps).
I've read a few posts with similar questions:
Winforms user controls custom events
Although I'm not quite sure if my problem has the same solution. How do I make it work? Here is my code for clarity:
class CueTextBox : TextBox
{
public string Cue
{
get { return Cue; }
set { Cue = value;}
}
public Color CueColor
{
get { return CueColor; }
set { CueColor = value; }
}
private void CueTextBox_Enter(object sender, EventArgs e)
{
TextBox t = sender as TextBox;
if (t.ForeColor == this.CueColor)
{
t.Text = "";
t.ForeColor = this.ForeColor;
}
}
private void CueTextBox_Leave(object sender, EventArgs e)
{
TextBox t = sender as TextBox;
if (t.Text.Trim().Length == 0)
{
t.Text = Cue;
t.ForeColor = this.CueColor;
}
}
}
The only thing that I see in your code is that the property definitions are recursively calling themselves and this will cause a stack overflow when adding the control to the design surface.
public string Cue
{
get { return Cue; }
set { Cue = value;}
}
Either define a backing field or use auto-implemented properties.
private string cue = String.Empty;
public string Cue
{
get { return cue; }
set { cue = value; }
}
or
public string Cue { get; set; }
Your question implied adding event handlers caused the issue. This can be a problem for custom controls at times. There is the Control.DesignMode property that is meant to allow conditional execution of code. However, it does not operate in the constructor. You need to do a bit of a hack to determine if the IDE is active.
This property can be used for development in Visual Studio as an alternative to DesignMode.
private bool InDesignMode
{
get
{
return (System.ComponentModel.LicenseManager.UsageMode == System.ComponentModel.LicenseUsageMode.Designtime) ||
base.DesignMode ||
(System.Diagnostics.Process.GetCurrentProcess().ProcessName == "devenv");
}
}
In solution development of custom controls is an exercise in self abuse. You are better of to go to Project Properties->Debug Tab and set the "Start Action" to "Start External Program" with "devenv.exe" as the program. This will start a new instance of VS when you "run" the debugger. When you add a control to the design surface of the new VS instance, you can debug your control's code. Break points will be hit and exceptions displayed.

How to enable a button to when one of my textbox changed in C#?

Today I got a problem in my development.
I have a Windows Form like this :
I need to enable the button "Appliquer" when the content of one of my textbox change.
I know that I can put the KeyPress event on each textbox and enable my button with that. In this window it can be easy to do that because there is only 10 textbox but I have an other window with more of 100 textbox and I think there is a better solution.
I tried to put the Keydown event directly in my windows form but it doesn't work.
So my question is, how can I do this. If someone have an idea ?
Thank you in advance !
Thomas
Since you already have 100+ textboxes in your form. I am assuming performance is not an issue for you.
In your form constructor, call this method. It will attach the event to all the textbox controls present in your form & inside sub controls such as groupbox, panel etc. (if you require)
There could be better ways of iteration..
public Form1()//your constructor
{
InitializeComponent();
AttachEvent(this);
}
void AttachEvent(Control CTrl)
{
foreach (Control c in CTrl.Controls)
{
if (c is TextBox)
{
c.TextChanged += new EventHandler(c_TextChanged);
continue;
}
if (c.HasChildren)
{
AttachEvent(c);
}
}
}
void c_TextChanged(object sender, EventArgs e)
{
//Your Code here btnGo.Enabled = !btnGo.Enabled;
}
What you can do is to extend TextBox make a field ( accessible from the designer ) to bind that TextBox into some other control.
public class MeTextBox
: TextBox
{
public override string Text
{
get { return base.Text; }
set
{
if ( m_DependantControl != null )
{
m_DependantControl.Enabled = !string.IsNullOrWhiteSpace(value);
}
base.Text = value;
}
}
Control m_DependantControl;
[Browsable(true)]
public Control DependantControl
{
get { return m_DependantControl; }
set { m_DependantControl = value; }
}
}
Now you can use MeTextBox as a regular TextBox. And if you want to make it control Enabled flag of some other Control you can just specify DependantControl property which will be accessible in the designer.
Fitting this into your example (code):
// assume you have a Button named btnConfirm
// and want to enable this button only when your `TextBox` has some text
MeTextBox mtb = new MeTextBox();
mtb.DependantControl = btnConfirm;
And if you do not want to make it in the code you can use designer directly.
To make it other way around ( one button dependant on many text boxes ) you can extend Button object :
public class MeButton
: Button
{
List<TextBox> m_DependantOn = new List<Control>();
[Browsable(true)]
public List<TextBox> DependantOn
{
get { return m_DependantOn; }
set { RemoveEvents(); m_DependantOn = value; AssignEvents(); }
}
void RemoveEvents()
{
foreach(TextBox ctrl in m_DependantOn)
ctrl.TextChanged -= WhenTextChanged;
}
void AssignEvents()
{
foreach(TextBox.ctrl in m_DependantOn)
ctrl.TextChanged += WhenTextChanged;
}
void WhenTextChanged(object sender, TextChangedEventArgs e)
{
this.Enabled = true;
}
}

Is there a way to convert a Controls.ContextMenu to a Forms.ContextMenu?

I have a context menu defined in my WPF XAML that looks like this:
<Window.Resources>
<ContextMenu x:Key="MyMenu">
<MenuItem Header="{x:Static props:Resources.MenuItem1}"/>
</ContextMenu>
</Window.Resources>
I'm using a System.Windows.Forms.NotifyIcon "myIcon" for my tray icon because it's so trivial to setup and use and because there seems to be no standard MSFT WPF equivalent. Unfortunately I get a casting exception when I call
this.myIcon.ContextMenu = (ContextMenu)this.Resources["MyMenu"];
because they're obviously not the same ContextMenu class. Is there a way to simply convert from the Controls.ContextMenu to a Forms.ContextMenu?
I'd prefer not to handle the right click mouse event of the notify icon by manually bringing up the context menu defined in my XAML. The reason being I suspect that the right click mouse event is not sent when the user uses the context menu key on the keyboard.
No - the controls are for completely different platforms (Winforms vs WPF). There is no "conversion" between the two.
Either use a different version of NotifyIcon that supports WPF or write a "conversion" that translates the items of the WPF context menu and adds them to the Winforms context menu.
So, I case anybody is curious. I ended up implementing a converter.
using System;
using System.Drawing;
using System.Windows.Controls;
namespace MyApp
{
class NotifyIconEx
{
#region Data
private System.Windows.Forms.NotifyIcon _notifyIcon = new System.Windows.Forms.NotifyIcon();
#endregion // Data
#region Properties
public Icon Icon
{
get { return _notifyIcon.Icon; }
set { _notifyIcon.Icon = value; }
}
public ContextMenu ContextMenu
{
private get { return null; }
set
{
_notifyIcon.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();
foreach (var item in value.Items)
{
if (item is MenuItem)
{
var menuItem = item as MenuItem;
var toolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
toolStripMenuItem.Click += (s, e) => menuItem.RaiseEvent(new System.Windows.RoutedEventArgs(MenuItem.ClickEvent));
toolStripMenuItem.Text = menuItem.Header as string;
_notifyIcon.ContextMenuStrip.Items.Add(toolStripMenuItem);
}
else if (item is Separator)
{
_notifyIcon.ContextMenuStrip.Items.Add(new System.Windows.Forms.ToolStripSeparator());
}
else
{
throw new NotImplementedException();
}
}
}
}
public bool Visible
{
get { return _notifyIcon.Visible; }
set { _notifyIcon.Visible = value; }
}
#endregion // Properties
#region API
public void ShowBalloonTip(int timeout)
{
_notifyIcon.ShowBalloonTip(timeout);
}
public void ShowBalloonTip(int timeout, string tipTitle, string tipText, System.Windows.Forms.ToolTipIcon tipIcon)
{
_notifyIcon.ShowBalloonTip(timeout, tipTitle, tipText, tipIcon);
}
#endregion // API
}
}

Categories

Resources