C# Custom ComboBox - DropDown Position - c#

I'm creating a ComboBox control using ToolStripControlHost and ToolStripDropDown that can host any kind of control in the DropDown window. For example, the DropDown window might display a listview or treeview or even another usercontrol.
I'm posting a simplified code below where dropdown host a usercontrol with a listview and a button like this:
The problem occurs when the control is positioned at the bottom of the screen in such a way that the dropdown window will extrapolate the lower boundary of the screen. When this occurs, the dropdown ends up hiding the control.
In this case, I'd like to fix the _dropDown.Show method call to show dropdown window as follows:
To repeat the problem, just run the code below and drag the window to the bottom of the screen and open the dropdown.
using System;
using System.Windows.Forms;
public class CustomComboBox : UserControl
{
ToolStripDropDown _dropDown;
public CustomComboBox()
{
var textbox = new TextBox();
textbox.Location = new System.Drawing.Point(0, 0);
textbox.Size = new System.Drawing.Size(this.Width - 22, 20);
textbox.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
this.Controls.Add(textbox);
var button = new Button();
button.Location = new System.Drawing.Point(this.Width - 22, -1);
button.Size = new System.Drawing.Size(22, 22);
button.Text = "\u2BC6";
button.Anchor = AnchorStyles.Right | AnchorStyles.Top;
button.Click += new System.EventHandler(this.Button_Click);
this.Controls.Add(button);
var dropDownControl = new DropDownControlTest();
var controlHost = new ToolStripControlHost(dropDownControl);
_dropDown = new ToolStripDropDown();
_dropDown.AutoSize = true;
_dropDown.Items.Add(controlHost);
}
void Button_Click(object sender, EventArgs e)
{
_dropDown.Show(this, 0, this.Height);
}
}
public class DropDownControlTest : UserControl
{
public DropDownControlTest()
{
var listview = new ListView();
listview.Location = new System.Drawing.Point(3, 1);
listview.Size = new System.Drawing.Size(400,300);
listview.View = View.Details;
listview.Columns.Add("Col 1",100);
listview.Columns.Add("Col 2",100);
this.Controls.Add(listview);
var button = new Button();
button.Location = new System.Drawing.Point(3, 305);
button.Text = "More...";
this.Controls.Add(button);
}
}
public class Form1 : Form
{
private static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
public Form1 ()
{
CustomComboBox ccBox = new CustomComboBox();
ccBox.Location = new System.Drawing.Point(10, 10);
ccBox.Height = 20;
this.Text = "Test CustomComboBox";
this.Controls.Add(ccBox);
}
}

You can use the ToolStripDropDown.Show Method (Control, Point, ToolStripDropDownDirection) overload to control the drop direction. The code will need to perform bounds checking to decide whether to place the dropdown above or below the textbox.
The following is a simplistic method for doing the bounds checking and was only tested on a single screen configuration.
First, make textbox a class level variable.
private TextBox textbox;
public CustomComboBox()
{
//var textbox = new TextBox();
textbox = new TextBox();
The display logic is as follows.
void Button_Click(object sender, EventArgs e)
{
Point textBoxScreenLocation = textbox.PointToScreen(textbox.Location);
// try to position _dropDown below textbox
Point pt = textBoxScreenLocation;
pt.Offset(0, textbox.Height);
// determine if it will fit on the screen below the textbox
Size dropdownSize = _dropDown.GetPreferredSize(Size.Empty);
Rectangle dropdownBounds = new Rectangle(pt, dropdownSize);
if (dropdownBounds.Bottom <= Screen.GetWorkingArea(dropdownBounds).Bottom)
{ // show below
_dropDown.Show(pt, ToolStripDropDownDirection.BelowRight);
}
else
{ // show above
_dropDown.Show(textBoxScreenLocation, ToolStripDropDownDirection.AboveRight);
}
}
}

I can not comment that is why I am answering your question. You can use the reflection and then re-position you control. I have found a custom combobox control same as you developed. Please check this. At least, you will get some idea what you need to do.

Related

SelectAll method in custom TextBox (Winforms .NET 7)

I have made a Custom TextBox that is a panel with a TextBox inside.
What i'm trying to do is [CustomTextBox name].SelectAll(); where it
returns [TextBox nameInsideCustomControl].SelectAll();
[...] is just a name.
How can i implement it?
I have tried:
[Category("Custom")]
public void SelectAll
{
get { textBox1.SelectAll(); }
}
But i don't think it's right.
Answer:
public void SelectAll() => textBox1.SelectAll();. It's a method, not a property. –
dr.null
You have a CustomTextBox that inherits from Panel and you want to be able to select all text in the TextBox that it contains.
To achieve this objective, first make sure that your CustomTextBox exposes a pubic method named SelectAll that forwards the command to the private TextBox it contains. But in addition to that you will either need to make sure that the internal TextBox receives focus or set the HideSelection property of the internal TextBox to false. Otherwise you may still not see the selection highlighted.
Custom Text Box example
public partial class CustomTextBox : Panel
{
public void SelectAll()
{
textBox1.SelectAll();
// Ensure text box gets the focus.
textBox1.Focus();
}
public void LoadTestData() =>
textBox1.Text = "Don't forget to give Focus to the TextBox!";
public CustomTextBox()
{
BackColor= Color.Azure;
label1 = new System.Windows.Forms.Label();
textBox1 = new System.Windows.Forms.TextBox();
#region I N I T I A L I Z E
SuspendLayout();
label1.AutoSize = true;
label1.Location = new System.Drawing.Point(12, 17);
label1.Size = new System.Drawing.Size(143, 25);
label1.TabIndex = 0;
label1.Text = "Custom Text Box";
textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
textBox1.Location = new System.Drawing.Point(190, 17);
textBox1.Multiline = true;
textBox1.Size = new System.Drawing.Size(206, 126);
textBox1.TabIndex = 1;
textBox1.HideSelection = false;
Controls.Add(textBox1);
Controls.Add(label1);
Name = "CustomTextBox";
Size = new System.Drawing.Size(424, 157);
ResumeLayout(false);
PerformLayout();
#endregion I N I T I A L I Z E
}
private Label label1;
private TextBox textBox1;
}
Minimal Test Code
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
customTextBoxPanel.LoadTestData();
buttonSelectAllTest.Click += onClickSelectAllTest;
}
private void onClickSelectAllTest(object? sender, EventArgs e)
{
customTextBoxPanel.SelectAll();
}
}

Repositioning element on the form

I'm building that To-Do list app and I'm trying to create a "Add New task" button.
I wanted to create a new Task (TextBox) on the button click and also move the button bellow the TextBox added. So, I tried to create multiple panels and when the button was clicked, move the "New Task" button to the second panel and add the new textbox created to the first panel. And after that if the user wanted to remove that Task (TextBox), to repositionate the button and Tasks according to that.
Can someone please help me with that??
`
private void btnNewTask_Click(object sender, EventArgs e)
{
// Creating new Textbox
TextBox txtTask = new TextBox();
btnNewTask.Location = new Point(panel2);
// txtTask parameter
txtTask.BorderStyle = BorderStyle.None;
txtTask.ReadOnly = false;
txtTask.PlaceholderText = "Input a new Task.";
txtTask.Location = new Point(panel1);
//Adding txtTask.
this.Controls.Add(txtTask);
}
`
I founded The Solution. I deleted the panels and leaved the btnNewTask on the form.
On button click I needed to create and set the txtTask location as the btnNewTask location and change the Y location of btnNewTask to move it bellow. After that check if there was any other control that had the same or less Y location than txtTask, and if there was any, add 35 to his Y location too.
There is my new Code:
int c = 0;
private void btnNewTask_Click(object sender, EventArgs e)
{
// Creating new Textbox
TextBox txtTask = new TextBox();
// txtTask parameter
Font font = new Font("Segoe UI", 10.0f);
txtTask.Name = "txtTask" + ++c;
txtTask.BorderStyle = BorderStyle.None;
txtTask.ReadOnly = false;
txtTask.PlaceholderText = "Input a new Task.";
txtTask.Size = new Size(817, 31);
txtTask.Font = font;
txtTask.Location = new Point(btnNewTask.Location.X , btnNewTask.Location.Y); // txtTask will get the "btnNewTask" Location
btnNewTask.Location = new Point(txtTask.Location.X , btnNewTask.Location.Y + 35); // That should add 35 to the Y location
foreach (Control item in this.Controls)
{
if (item.Location.Y >= txtTask.Location.Y)
{ // if there is an item that has greater Y location
item.Location = new Point(item.Location.X, txtTask.Location.Y + 35); // It should increase its value as 35 too.
}
this.Controls.Add(txtTask);
}
There's an example:
Thank for the help and ideas!

how can i call a button click method from another class

hello everyone i'm new to c# and wpf programming and
i'm trying to create a dynamic menu where i have + and - buttons which affect a text box which represents quantity. so in a grid i call a class called productcard which i call in a page to fill the grid with the products.
now the problem is how can i use the click event inside of the product card class in my page where i have multiple cards.
class productcard
{
Button plus = new Button();
Button minus= new Button();
public TextBox qtyl = new TextBox();
Grid z = new Grid();
public int left;
public int top;
GroupBox yy;
public GroupBox XX { get { return this.yy; } set { this.yy = value; } }
public productcard(int left , int top )
{
this.left = left;
this.top = top;
Thickness margin = new Thickness(left, top, 0, 0);
Thickness bmar = new Thickness(0, 0, 0, 0);
plus.Height = 30;
plus.Width = 40;
plus.VerticalAlignment = VerticalAlignment.Bottom;
plus.HorizontalAlignment = HorizontalAlignment.Right;
plus.Content = "+";
plus.HorizontalContentAlignment = HorizontalAlignment.Center;
// - button
minus.Height = 30;
minus.Width = 40;
minus.VerticalAlignment = VerticalAlignment.Bottom;
minus.HorizontalAlignment = HorizontalAlignment.Left;
minus.Content = "-";
minus.HorizontalContentAlignment = HorizontalAlignment.Center;
// add the button to the grid
z.Children.Add(plus);
z.Children.Add(minus);
// creat text box
qtyl = new TextBox();
qtyl.Height = 30;
qtyl.Width = 30;
qtyl.Background = Brushes.White;
qtyl.VerticalAlignment = VerticalAlignment.Bottom;
qtyl.HorizontalAlignment = HorizontalAlignment.Center;
qtyl.Text = "0";
// add text box to the grid inside the group box
z.Children.Add(qtyl);
// creat group box
GroupBox yy = new GroupBox();
yy.Margin = margin;
yy.VerticalAlignment = VerticalAlignment.Top;
yy.HorizontalAlignment = HorizontalAlignment.Left;
yy.Content = z;
yy.Height = 150;
yy.Width = 150;
XX = yy;
// insert group box in the produc grid
}
public void plus_Click(object sender, EventArgs e)
{
// this.plus.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
MessageBox.Show(" + has been cliked");
int result=Convert.ToInt32(qtyl.Text)+1;
qtyl.Text = result.ToString();
}
private void minus_Click(object sender, EventArgs e)
{
int result = Convert.ToInt32(qtyl.Text) - 1;
qtyl.Text = result.ToString();
}
}
You can make a handler for your button like this:
Button myButton=new Button();
myButton.Click += delegate(object sender, RoutedEventArgs e) {
//handle event
};
I hope this helps.
Reza is correct in how to write more code for a Button generated in code.
However, I would give you a word of warning that this is not proper WPF usage of MVVM and you might be putting yourself down a path for trouble later.
I would suggest having your view's buttons bind to an ICommand that can be defined in a ViewModel that will handle doing the logic for your text update.
As you mentioned, you're having different view controls represent the data based on your button press. You're currently surviving as the view's are directly updating each other (THIS IS BAD).
The moment you want to represent this data in other views, say you want your button to update 5 labels in 3 different layouts in 2 windows, you're going to have unmaintainable references in your views.
If you have the ViewModel get a command from your view bound to the button, you can have the command logic update the property in the ViewModel that multiple views can be bound to and update them all at the same time via INotifyPropertyChanged.
Not to mention, ICommand can also let you disable buttons cleanly from being clicked.
Consider taking an hour to check out this tutorial to see the separation of View and ViewModel. What you're doing now looks like it's setting you up for a world of hurt later...

Dynamically Adding Class of Controls

I am having trouble with dynamically adding a class of controls that should when working look like this:
When a new one is added it should appear in the left panel under the toolstrip.
So far I am having trouble making them appear (The one in the middle is just the design I made).
Here is the code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//Problem Occurs Here
EquationBox[] EquationBoxArray = new EquationBox[12];
for (int x = 0; x < 12; x++)
{
EquationBoxArray[x] = new EquationBox();
ActiveForm.Controls.Add(EquationBoxArray[x].mainPanel);
ActiveForm.Controls.Add(EquationBoxArray[x].colorPanel);
}
}
private void add_line_Click(object sender, EventArgs e) //Add Line
{
}
private void clear_Click(object sender, EventArgs e) //Clear Lines
{
}
}
public class EquationBox
{
public Panel colorPanel = new Panel();
public Panel mainPanel = new Panel();
public TextBox equationBox = new TextBox();
public CheckBox isVisibleBox = new CheckBox();
public EquationBox()
{
mainPanel.Size = new Size(200, 72);
colorPanel.Size = new Size(33, 72);
mainPanel.Location = new Point(50, 50);
colorPanel.Location = new Point(50, 50);
colorPanel.BackColor = Color.Red;
}
}
The problem occurs here:
//Problem Occurs Here
EquationBox[] EquationBoxArray = new EquationBox[12];
for (int x = 0; x < 12; x++)
{
EquationBoxArray[x] = new EquationBox();
ActiveForm.Controls.Add(EquationBoxArray[x].mainPanel);
ActiveForm.Controls.Add(EquationBoxArray[x].colorPanel);
}
When I run it, it return with:
Additional information: Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.
And even before that started happening, the EqautionBox wouldn't appear.
Thanks in advance, this is really troubling me.
For the constructor of EquationBox:
public EquationBox()
{
mainPanel.Size = new Size(200, 72);
colorPanel.Size = new Size(33, 72);
mainPanel.Location = new Point(50, 50);
colorPanel.Location = new Point(50, 50);
colorPanel.BackColor = Color.Red;
}
First, your control appeared, but mainPanel is overlap colorPanel and you can't see mainPanel (same BG color as your form), so swap which added first solved
EquationBox[] EquationBoxArray = new EquationBox[12];
for (int x = 0; x < 12; x++)
{
EquationBoxArray[x] = new EquationBox();
this.Controls.Add(EquationBoxArray[x].colorPanel);
this.Controls.Add(EquationBoxArray[x].mainPanel);
}
I am using this.Controls, not sure about the ActiveForm.Controls part, maybe on constructing, your Form1 is not the active one, so error occured.
Ps: I suggest add colorPanel to mainPanel, and only add mainPanel to Form. And UserControl is a good solution here as Steve Wellens said.
There are various issues with EquationBox the TextBox and CheckBox are not in the panel. It would be easier to make it a UserControl.
Then to do the positioning use a FlowLayoutPanel.

Question about an event perculiarity in a TabControl

I have a little demonstration below of a peculiar problem.
using System;
using System.Windows.Forms;
namespace WindowsApplication1
{
public class TestForm : Form
{
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabPage tabPage1;
private System.Windows.Forms.TabPage tabPage2;
private System.Windows.Forms.TextBox textBox1;
public TestForm()
{
//Controls
this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage();
this.tabPage2 = new System.Windows.Forms.TabPage();
this.textBox1 = new System.Windows.Forms.TextBox();
// tabControl1
this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Location = new System.Drawing.Point(12, 12);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(260, 240);
this.tabControl1.TabIndex = 0;
this.tabControl1.Selected += new System.Windows.Forms.TabControlEventHandler(this.tabControl1_Selected);
// tabPage1
this.tabPage1.Controls.Add(this.textBox1);
this.tabPage1.Location = new System.Drawing.Point(4, 22);
this.tabPage1.Name = "tabPage1";
this.tabPage1.Size = new System.Drawing.Size(252, 214);
this.tabPage1.TabIndex = 0;
this.tabPage1.Text = "tabPage1";
// tabPage2
this.tabPage2.Location = new System.Drawing.Point(4, 22);
this.tabPage2.Name = "tabPage2";
this.tabPage2.Size = new System.Drawing.Size(192, 74);
this.tabPage2.TabIndex = 1;
this.tabPage2.Text = "tabPage2";
// textBox1
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Location = new System.Drawing.Point(6, 38);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(240, 20);
this.textBox1.TabIndex = 0;
// TestForm
this.ClientSize = new System.Drawing.Size(284, 264);
this.Controls.Add(this.tabControl1);
this.Name = "Form1";
this.Text = "Form1";
}
//Tab Selected
private void tabControl1_Selected(object sender, EventArgs e)
{
this.Text = "TextBox Width: " + this.textBox1.Width.ToString();
}
}
//Main
static class Program
{
static void Main()
{
Application.Run(new TestForm());
}
}
}
If you run the above C# code you will have a small form containing a tabcontrol. Within the tabcontrol is a texbox on the first tab. If you follow these steps you will see the problem:
Select tabPage2 (textBox1's width is reported in the form title)
Resize the form
Select tabPage1 (The wrong textBox1 width is reported)
Any ideas what is going on here? The textbox is obviously bigger than what is being reported. If you click again on tabPage2 the correct size is then updated. Obviously there is an event updating the width of textBox1. Can i trigger this when tabPage1 is selected?
Firstly, thanks for the complete program - it made it much easier to work out what was going on!
While the textbox isn't visible, it isn't resized. When you select tabPage1, the Selected event fires before the controls become visible and the textbox gets laid out again.
Now, that's why it's happening - but what's your real situation? If you actually want to capture the size of controls changing, subscribe to their Resize events. If not, could you explain more about what you're trying to achieve?
I'm pretty sure that what's happening is the Selected event is raised slightly before the tab page becomes visible. The text box is not resized until the tab page becomes visible, so you end up checking the value of the text box's size before it is actually resized. When you change tabs again, the text box is already resized, so you get the correct value.
Change the last few lines of your example form to look like this and it will become apparent:
this.textBox1.SizeChanged += TextboxSizeChanged;
}
//Tab Selected
private void tabControl1_Selected(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("tab selected");
this.Text = "TextBox Width: " + this.textBox1.Width.ToString();
}
private void TextboxSizeChanged(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Textbox resized");
}
If you modify your code a little by adding an event handler to the textbox1.Resize event you will see what happens.
The tabPage1.Selected event occurs before the controls in the tab page is resized so when you check the width of the textbox you are checking it before it is resized.
Normally this wouldn't be a problem, for the resizing is done properly afterwards, but I guess that you will be using the size of the textbox for something?
You should be able to write your own TabControl that fixes this problem, but you will have to experiment to see what works here.
Not sure if I understand the problem.
But, you might use textbox's resize event to capture the width change OR form's resize.
In your example, does the select event of tabPage1 fire when you do step 3?

Categories