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.
Related
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"/>
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;
}
}
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.
I have a second window which opens when a certain staffname is searched for, this window prompts you to choose between the 2 staff members with the same name. The window then needs to return a value to the parent window to populate a datatemplate with relating data from the xml file.
I've tried to create a string which will be updated with a value depending on which button is clicked, this string can then be returned to the calling method on the first window and populate binding data in the Linq to Xml query.
But when it runs it causes a stackoverflow exception and that it could be an infinite loop. I'm not sure enough about c# to know what to change.
public partial class Choice : Window
{
private string StaffChoice;
public Choice()
{
InitializeComponent();
}
public string staffChoice
{
get { return this.StaffChoice; }
set { staffChoice = StaffChoice; }
}
private void btnMRG_Click(object sender, RoutedEventArgs e)
{
StaffChoice = "MRG";
this.Close();
}
private void btnRPG_Click(object sender, RoutedEventArgs e)
{
StaffChoice = "RPG";
this.Close();
}
}
Any help or suggestions would be great!
Thanks in advance!
Firstly, your naming conventions are wrong - the field should be called staffChoice and the property should be called StaffChoice. Please read the .NET naming conventions for more information. However, now look at your property closely:
public string staffChoice
{
get { return this.StaffChoice; }
set { staffChoice = StaffChoice; }
}
What do you think the setter does? There are two problems with it:
It ignores the value that you're trying to set it to.
It calls itself recursively.
You could fix this by keeping the manually-declared field, fixing the naming conventions, and changing the property to set the variable to value like this:
private string staffChoice;
public string StaffChoice
{
get { return staffChoice; }
set { staffChoice = value; }
}
However, it would be simpler to use an automatically implemented property:
public string StaffChoice { get; set; }
This will create the backing field and the getter/setter for you automatically.
The simplest way is to declare a property like this...
public string StaffChoice { get; set; }
your problem is you are basically calling the property setter from within the same setter - thus you have a recursive loop. You could change your code like this to make it work...
private string StaffChoice;
public string staffChoice
{
get { return this.StaffChoice; }
set { StaffChoice = value; }
}
Your setter isn't right, you are assigning a value to itself (causing the infinite loop) and not using value.
You should change your code to this, your naming convention looked backwards so I corrected it, hope you don't mind:
private string staffChoice;
public Choice()
{
InitializeComponent();
}
public string StaffChoice
{
get { return staffChoice; }
set { staffChoice = value; }
}
private void btnMRG_Click(object sender, RoutedEventArgs e)
{
staffChoice = "MRG";
this.Close();
}
private void btnRPG_Click(object sender, RoutedEventArgs e)
{
staffChoice = "RPG";
this.Close();
}
Your property should be:
public string staffChoice
{
get { return this.StaffChoice; }
set { this.StaffChoice = value; }
}
In your code you are calling the setter again in the setter - hence the infinite recursion.
However, as you are not doing anything special in the setter (like notifying the UI that the property has changed you could simply have:
public string staffChoice { get; set; }
This "auto property" is a little cleaner.
(BTW: the normal practice is to have the back variable starting with a lower case letter and the public property starting with an upper case one. However, if you are consistent in your application it doesn't really matter.)
I'm a newbie in C# bu I'm experienced Delphi developer.
In Delphi I can use same code for MenuItem and ToolButton using TAction.OnExecute event and I can disable/enable MenuItem and ToolButton together using TAction.OnUpdate event.
Is there a similar way to do this in C# without using external libraries? Or more - How C# developers share code between different controls?
Ok, may be I write my question in wrong way. I want to know not witch property to use (I know about Enabled property) but I want to know on witch event I should attach to if I want to enable/disable more than one control. In delphi TAction.OnUpdate event ocurs when Application is idle - is there similar event in C#?
Try the a modification of the command pattern:
public abstract class ToolStripItemCommand
{
private bool enabled = true;
private bool visible = true;
private readonly List<ToolStripItem> controls;
protected ToolStripItemCommand()
{
controls = new List<ToolStripItem>();
}
public void RegisterControl(ToolStripItem item, string eventName)
{
item.Click += delegate { Execute(); };
controls.Add(item);
}
public bool Enabled
{
get { return enabled; }
set
{
enabled = value;
foreach (ToolStripItem item in controls)
item.Enabled = value;
}
}
public bool Visible
{
get { return visible; }
set
{
visible = value;
foreach (ToolStripItem item in controls)
item.Visible = value;
}
}
protected abstract void Execute();
}
Your implementations of this command can be stateful in order to support your view's state. This also enables the ability to build "undo" into your form. Here's some toy code that consumes this:
private ToolStripItemCommand fooCommand;
private void wireUpCommands()
{
fooCommand = new HelloWorldCommand();
fooCommand.RegisterControl(fooToolStripMenuItem, "Click");
fooCommand.RegisterControl(fooToolStripButton, "Click");
}
private void toggleEnabledClicked(object sender, EventArgs e)
{
fooCommand.Enabled = !fooCommand.Enabled;
}
private void toggleVisibleClicked(object sender, EventArgs e)
{
fooCommand.Visible = !fooCommand.Visible;
}
HelloWorldCommand:
public class HelloWorldCommand : ToolStripItemCommand
{
#region Overrides of ControlCommand
protected override void Execute()
{
MessageBox.Show("Hello World");
}
#endregion
}
It's unfortunate that Control and ToolStripItem do not share a common interface since they both have "Enabled" and "Visible" properties. In order to support both types, you would have to composite a command for both, or use reflection. Both solutions infringe on the elegance afforded by simple inheritance.
You can enable or disable a control and all its children by setting its Enabled property.
You can hook the code for the MenuItem and the ToolButton in the same handler. For example:
menuItem.Click += HandleClick;
toolbarButton.Click += handleClick;
This way clicking both the MenuItem and the Button will execute the same code and provide the same functionality.