I am adding a checkbox to a comboBox in a windowsform. The checkbox add and I can select them as Items but I can see the text of the box to tick. In the combobox drop down the items are list empty and clicking on them specific the correct details when incepting the selected item.
How do I make them visable to see the box and name?
You need to create your own user control. First step is to create subclass the System.Windows.Forms.ComboBox class:
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class CheckComboBox : ComboBox
{
public CheckComboBox()
{
this.DrawMode = DrawMode.OwnerDrawFixed;
}
}
}
You should set the DrawMode property to tell the ComboBox that we intend to render the drop-down list items ourselves. The next step was to define a class to contain our drop-down list item data and maintain the state. This is a simple class:
namespace WindowsFormsApp1
{
public class CheckComboBoxItem
{
public CheckComboBoxItem(string text, bool initialCheckState)
{
_checkState = initialCheckState;
_text = text;
}
private bool _checkState = false;
public bool CheckState
{
get { return _checkState; }
set { _checkState = value; }
}
private string _text = "";
public string Text
{
get { return _text; }
set { _text = value; }
}
public override string ToString()
{
return "Select Options";
}
}
}
After that go back to your CheckComboBox.cs and add delegates DrawItem and SelectedIndexChanged event.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace WindowsFormsApp1
{
public partial class CheckComboBox : ComboBox
{
public event EventHandler CheckStateChanged;
public CheckComboBox()
{
this.DrawMode = DrawMode.OwnerDrawFixed;
this.DrawItem += new DrawItemEventHandler(CheckComboBox_DrawItem);
this.SelectedIndexChanged += new EventHandler(CheckComboBox_SelectedIndexChanged);
}
void CheckComboBox_DrawItem(object sender, DrawItemEventArgs e)
{
if (e.Index == -1)
{
return;
}
if (!(Items[e.Index] is CheckComboBoxItem))
{
e.Graphics.DrawString(
Items[e.Index].ToString(),
this.Font,
Brushes.Black,
new Point(e.Bounds.X, e.Bounds.Y));
return;
}
CheckComboBoxItem box = (CheckComboBoxItem)Items[e.Index];
CheckBoxRenderer.RenderMatchingApplicationState = true;
CheckBoxRenderer.DrawCheckBox(
e.Graphics,
new Point(e.Bounds.X, e.Bounds.Y),
e.Bounds,
box.Text,
this.Font,
(e.State & DrawItemState.Focus) == 0,
box.CheckState ? CheckBoxState.CheckedNormal :
CheckBoxState.UncheckedNormal);
}
void CheckComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
CheckComboBoxItem item = (CheckComboBoxItem)SelectedItem;
item.CheckState = !item.CheckState;
CheckStateChanged?.Invoke(item, e);
}
}
}
In DrawItems delegate, the first thing we do is to verify that the item we are rendering was added as a CheckComboBoxItem. If it is not, we render it as a simple string. Otherwise, we get the appropriate CheckComboBoxItem from the Items collection (using the DrawItemEventArgs.Index property). Then we call the CheckBoxRenderer.DrawCheckBox() method, passing in the Graphics object, into which we want to render the CheckBox, and the location, size, text, font, focus and check states.
The second one allows us to toggle the check box in the drop-downs, but doesn't allow the user of this control to know that anything has happened. So we also add a public event to notify the control's users of a change to the check state of an item in the drop-down list:
public event EventHandler CheckStateChanged;
Finally, if you want to use this control, in the default Form1 of your application type this code:
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
checkComboBox1.Items.Add(new CheckComboBoxItem("One", true));
checkComboBox1.Items.Add(new CheckComboBoxItem("Two", true));
checkComboBox1.Items.Add(new CheckComboBoxItem("Three", true));
this.checkComboBox1.CheckStateChanged += new EventHandler(this.checkComboBox1_CheckStateChanged);
}
private void checkComboBox1_CheckStateChanged(object sender, EventArgs e)
{
if (sender is CheckComboBoxItem)
{
CheckComboBoxItem item = (CheckComboBoxItem)sender;
}
}
}
}
You have so many links that can be useful to you. You do not put any code so we don't know what exactly you need...
https://www.codeproject.com/Articles/31105/A-ComboBox-with-a-CheckedListBox-as-a-Dropdown
https://www.codeproject.com/Articles/21085/CheckBox-ComboBox-Extending-the-ComboBox-Class-and
https://www.codeproject.com/Articles/18929/An-OwnerDraw-ComboBox-with-CheckBoxes-in-the-Drop
Thank you #Rob and #Mamun for correcting me.
Related
I am using element host to use WPF spell checker textbox in my winforms.
I want to override the context menu that appears on misspelled red squiggles to mouse hover instead of right click.
How to do that?
Tried overriding the behavior but it is still the same:
using System;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms.Integration;
using System.Windows.Forms.Design;
[Designer(typeof(ControlDesigner))]
class SpellCheck: ElementHost
{
privated TextBox box;
public SpellCheck()
{
box = new TextBox();
base.Child = box;
box.TextChanged += (s, e) => OnTextChanged(EventArgs.Empty);
box.SpellCheck.IsEnabled = true;
box.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
}
[DefaultValue(false)]
public bool Multiline
{
//checks for multiline
}
public bool IsEnabled
{
//checks for spell check enabled or not
}
[DefaultValue(false)]
public bool WordWrap
{
//does wordwraps
}
[DefaultValue(false)]
public int MaxLength
{
//maxlength property
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new System.Windows.UIElement Child
{
get { return base.Child; }
set { }
}
}
It shows spell suggestions on right click. I want to change it to Mouse hover or any other mouse events.
You could use TextBox.PreviewMouseMove to show a popup to achieve this.
You also need to create a new Form to serve as a popup. This Form could contain a FlowLayoutPanel to host the suggestions. In this example the Form is named CustomPopup:
box.PreviewMouseMove += OpenContextMenuOnMouseMove;
The event handler for TextBox.PreviewMouseMove:
private void OpenContextMenuOnMouseMove(object sender, System.Windows.Input.MouseEventArgs mouseEventArgs)
{
var textBox = sender as System.Windows.Controls.TextBox;
var mouseOverTextIndex = textBox.GetCharacterIndexFromPoint(mouseEventArgs.GetPosition(textBox), false);
// Pointer is not over text
if (mouseOverTextIndex.Equals(-1))
{
return;
}
int spellingErrorIndex = textBox.GetNextSpellingErrorCharacterIndex(mouseOverTextIndex, LogicalDirection.Forward);
// No spelling errors
if (spellingErrorIndex.Equals(-1))
{
return;
}
int startOfWordIndex = mouseOverTextIndex;
while (startOfWordIndex != -1 && !textBox.Text[startOfWordIndex].Equals(' '))
{
startOfWordIndex--;
}
var endOfWordIndex = textBox.Text.IndexOf(" ", mouseOverTextIndex, StringComparison.OrdinalIgnoreCase);
if (endOfWordIndex.Equals(-1))
{
endOfWordIndex = textBox.Text.Length - 1;
}
// Spelling error doesn't belong to current mouse over word
if (spellingErrorIndex < startOfWordIndex || spellingErrorIndex > endOfWordIndex)
{
return;
}
using(CustomPopup popup = new CustomPopup())
{
// Create clickable suggestions for the CustomPopup.
foreach (var suggestion in textBox.GetSpellingError(spellingErrorIndex).Suggestions)
{
// Each Button represents a suggestion.
var suggestionButton = new System.Windows.Forms.Button() { Text = suggestion };
var fixSpellingErrorEventArgs = new FixSpellingErrorEventArgs()
{
TargetTextBox = textBox,
WordStartIndex = startOfWordIndex,
WordEndIndex = endOfWordIndex,
Suggestion = suggestion
};
// The Button.Click callback will apply the selected fix
suggestionButton.Click += (s, e) => FixSpellingError_OnButtonClicked(fixSpellingErrorEventArgs);
// TODO::Replace the line with a public member of CustomPopup Form: CustomPopup.AddPanelContent(Control):void.
// e.g. public void AddPanelContent(Control control) { this.FlowLayoutPanel1.Controls.Add(suggestionButton); }
// and use it like: popup.AddPanelContent(suggestionButton);
popup.FlowLayoutPanel1.Controls.Add(suggestionButton);
}
popup.SetDesktopLocation((int) mouseEventArgs.GetPosition(textBox).X, (int) mouseEventArgs.GetPosition(textBox).Y);
popup.ShowDialog(this);
}
}
// The event handler that applies the selected fix.
// Invoked on popup button clicked.
private void FixSpellingError_OnButtonClicked(FixSpellingErrorEventArgs e)
{
// Select misspelled word and replace it with the selected fix
e.TargetTextBox.SelectionStart = e.WordStartIndex;
e.TargetTextBox.SelectionLength = e.WordEndIndex - e.WordStartIndex;
e.TargetTextBox.SelectedText = e.Suggestion;
}
The custom event arg object for Button.Click event
class FixSpellingErrorEventArgs : EventArgs
{
public System.Windows.Controls.TextBox TargetTextBox { get; set; }
public int WordStartIndex { get; set; }
public int WordEndIndex { get; set; }
public string Suggestion { get; set; }
}
To enhance the example create the logic how or when the popup will disappear (time out and focus lost?).
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.
I have a ToolStripSplitButton with various elements in dropdown list.
One of them is a Trackbar enclosed in a ToolStripControlHost, called ToolStripTrackbarItem. It's code (I've got it from stackoverflow):
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;
namespace Application
{
[System.ComponentModel.DesignerCategory("code")]
[System.Windows.Forms.Design.ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.ContextMenuStrip | ToolStripItemDesignerAvailability.MenuStrip)]
public class ToolStripTrackbarItem : ToolStripControlHost
{
public ToolStripTrackbarItem()
: base(CreateControlInstance())
{
this.Size = Control.Size;
}
public TrackBar TrackBar
{
get { return Control as TrackBar; }
}
private static Control CreateControlInstance()
{
TrackBar t = new TrackBar();
t.AutoSize = false;
return t;
}
[DefaultValue(0)]
public int Value
{
get { return TrackBar.Value; }
set { TrackBar.Value = value; }
}
protected override void OnSubscribeControlEvents(Control control)
{
base.OnSubscribeControlEvents(control);
TrackBar trackBar = control as TrackBar;
trackBar.ValueChanged += new EventHandler(trackBar_ValueChanged);
}
protected override void OnUnsubscribeControlEvents(Control control)
{
base.OnUnsubscribeControlEvents(control);
TrackBar trackBar = control as TrackBar;
trackBar.ValueChanged -= new EventHandler(trackBar_ValueChanged);
}
void trackBar_ValueChanged(object sender, EventArgs e)
{
if (this.ValueChanged != null)
ValueChanged(sender, e);
}
public event EventHandler ValueChanged;
protected override Size DefaultSize
{
get { return new Size(300, 16); }
}
}
It works, but I need to show images to the left of the dropdown items:
I'm successful with a simple ToolStripMenuItem by setting the Image property. However, it is ineffective to set Image property of my ToolStripTrackbarItem (that is inherited from ToolStripControlHost, see code above). According to MSDN, Image property is irrelevant to ToolStripControlHost.
What does it mean? Is it not even possible to include an image left to ToolStripControlHost?
If it is possible anyway, how to do that?
You should solve 2 problems here:
ToolStripControlHost doesn't show Image property and also doesn't serialize the image when you save the form.
ToolStripProfessionalRendered doesn't draw image for ToolStripControlHost.
You need to override Image property of ToolStripControlHost and make it browsable and serializable. Also you need to create a custom renderer to draw the image in the correct location and size. Then if you simply set the renderer for ToolStrip using below code, you will get expected result:
this.toolStrip1.Renderer = new MyCustomRenderer();
ToolStripTrackBar
The item enables Image property to show in property grid and let it serialize when saving form.
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.Design;
[ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.All)]
public class ToolStripTrackBar : ToolStripControlHost
{
public TrackBar TrackBar { get { return (TrackBar)Control; } }
public ToolStripTrackBar() : base(CreateControl()) { }
private static TrackBar CreateControl()
{
var t = new TrackBar()
{ TickStyle = TickStyle.None, AutoSize = false, Height = 28 };
return t;
}
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public override Image Image
{
get { return base.Image; }
set { base.Image = value; }
}
/*Expose properties and events which you need.*/
public int Value
{
get { return TrackBar.Value; }
set { TrackBar.Value = value; }
}
}
MyCustomRenderer
This renderer draws images for ToolStripTrackBar.
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
public class MyCustomRenderer : ToolStripProfessionalRenderer
{
protected override void OnRenderImageMargin(ToolStripRenderEventArgs e)
{
base.OnRenderImageMargin(e);
e.ToolStrip.Items.OfType<ToolStripTrackBar>()
.ToList().ForEach(item =>
{
if (item.Image != null)
{
var size = item.GetCurrentParent().ImageScalingSize;
var location = item.Bounds.Location;
location = new Point(5, location.Y + 1);
var imageRectangle = new Rectangle(location, size);
e.Graphics.DrawImage(item.Image, imageRectangle,
new Rectangle(Point.Empty, item.Image.Size),
GraphicsUnit.Pixel);
}
});
}
}
Question: I have a Winform C# project that changes the tool tip text of a user control tool tip when passed from a host project. I need to pass font size as a variable, but dont know where to make the changes. I have tried a plethora of solutions online and am stuck with this implementation. Any help in this direction will be really appreciated.
What I have so far:
I have a C# user Control project where i have set a property to set the tool tip text to change to user specified value in my UC_ToolTipButton project, where the contents of the project are as below:
UC_ToolTipButton.cs
using System.Windows.Forms;
namespace UC_ToolTipButton
{
public partial class UC_ToolTipButton : UserControl
{
public string TT_Message
{
get{
return ToolTip_Message.GetToolTip(btnTT);
}
set{
ToolTip_Message.SetToolTip(btnTT, value);
}
}
public UC_ToolTipButton()
{
InitializeComponent();
}
}
}
In my designer file, I have placed a button (btnTT) on which I have put a tool tip (ToolTip_Message).
When I compile this User Control Forms project, it works fine and creates a dll file.
Upon importing this file in a project TryButtonTooltip, where I have the file TryTooltipForm.cs with the following content
using System.Windows.Forms;
namespace TryButtonToolTip
{
public partial class TryToolTipForm : Form
{
public TryToolTipForm()
{
InitializeComponent();
uC_TTMessage.TT_Message = #"Hi";
}
}
}
Set OwnerDraw on ToolTip to true, in ToolTip's Draw event set the desired font, then in Popup event measure and set the size of your ToolTip, as is explained in the example here.
For example like this (untested):
public partial class UC_ToolTipButton : UserControl
{
public string TT_FontFamily { get; set; }
public float TT_FontSize { get; set; }
public string TT_Message
{
get
{
return ToolTip_Message.GetToolTip(btnTT);
}
set
{
ToolTip_Message.SetToolTip(btnTT, value);
}
}
public UC_ToolTipButton()
{
InitializeComponent();
TT_FontFamily = "Tahoma";
TT_FontSize = 10;
ToolTip_Message.OwnerDraw = true;
ToolTip_Message.Draw += new DrawToolTipEventHandler(TT_Draw);
ToolTip_Message.Popup += new PopupEventHandler(TT_Popup);
}
private void TT_Popup(object sender, PopupEventArgs e)
{
using (Font f = new Font(TT_FontFamily, TT_FontSize))
{
e.ToolTipSize = TextRenderer.MeasureText(ToolTip_Message.GetToolTip(e.AssociatedControl), f);
}
}
private void TT_Draw(System.Object sender,
System.Windows.Forms.DrawToolTipEventArgs e)
{
e.DrawBackground();
e.DrawBorder();
using (StringFormat sf = new StringFormat())
{
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
using (Font f = new Font(TT_FontFamily, TT_FontSize))
{
e.Graphics.DrawString(e.ToolTipText, f, SystemBrushes.ActiveCaptionText, e.Bounds, sf);
}
}
}
}
For my windows forms application I tried to retrieve the selected string from a listBox and i wanted it to compare to a set string so that, in the event of the comparison returning true, i can set the next listBox to have specific, selection-related values.
namespace PhysCalc
{
public class Selectors
{
public static string[] topicContents = new string[] { "MECHANICS", "THEORY_OF_RELATIVITY" };
public static string[] VarItemsMechanics = new string[] { "Test", "Wavelength" };
public static void SetVarBox()
{
PhysCalc.Topic.DataSource = topicContents;
if PhysCalc.Topic.Items[PhysCalc.Topic.SelectedIndex].ToString() == "MECHANICS")
{
PhysCalc.Var.DataSource = VarItemsMechanics;
}
}
}
}
But somehow when i select "MECHANICS" in the listBox(in the code above named 'Topic'), the 2nd listBox(above named 'Var') just stays empty
any help would be very appreciated
I think you need to set the DisplayMember and ValueMember properties on the Var list control when using the DataSource.
If the DataSource is an object then DisplayMember is the object member it will use as the text display (which in your case is currently blank), and ValueMember is used to determine the SelectedValue property of the list control which is useful to bind against.
For example if your VarItemsMechanics is populated with the following class:
public class Mechanic
{
public int ID { get; set; }
public string Name { get; set; }
}
Then you probably want to set the DisplayMember to "Name" and you might want to set the ValueMember to "ID" (subjective).
Try changing
if (PhysCalc.Topic.GetItemText(PhysCalc.Topic.SelectedItem) == "MECHANICS")
to this:
if (PhysCalc.Topic.Items[PhysCalc.Topic.SelectedIndex].ToString() == "MECHANICS")
1- Bind your First List named "Topic" in the form_load Event
2- in the SelectedIndexChanged Event of your First List do your checks about the selected Item
and Fill the Second List
Here is the Complete Code and it works fine for me
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public static string[] topicContents = new string[] { "MECHANICS", "THEORY_OF_RELATIVITY" };
public static string[] VarItemsMechanics = new string[] { "Test", "Wavelength" };
private void Form1_Load(object sender, EventArgs e)
{
listBox1.DataSource = topicContents;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
string curItem = listBox1.SelectedItem.ToString();
switch (curItem)
{
case "MECHANICS":
listBox2.DataSource = VarItemsMechanics;
break;
}
}
}
}