PictureBox loses resize option when in panel - c#

I want to resize and move objects with mouse at runtime. I am using this code to move and resize controls. But when I put picturebox in panel it loses its resize option from right border and bottom border. What is happening and any solutions?
I got this code from some other forum:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Windows.Forms;
namespace ControlManager
{
internal class ControlMoverOrResizer
{
private static bool _moving;
private static Point _cursorStartPoint;
private static bool _moveIsInterNal;
private static bool _resizing;
private static Size _currentControlStartSize;
internal static bool MouseIsInLeftEdge { get; set; }
internal static bool MouseIsInRightEdge { get; set; }
internal static bool MouseIsInTopEdge { get; set; }
internal static bool MouseIsInBottomEdge { get; set; }
internal enum MoveOrResize
{
Move,
Resize,
MoveAndResize
}
internal static MoveOrResize WorkType { get; set; }
internal static void Init(Control control)
{
Init(control, control);
}
internal static void Init(Control control, Control container)
{
_moving = false;
_resizing = false;
_moveIsInterNal = false;
_cursorStartPoint = Point.Empty;
MouseIsInLeftEdge = false;
MouseIsInLeftEdge = false;
MouseIsInRightEdge = false;
MouseIsInTopEdge = false;
MouseIsInBottomEdge = false;
WorkType = MoveOrResize.MoveAndResize;
control.MouseDown += (sender, e) => StartMovingOrResizing(control, e);
control.MouseUp += (sender, e) => StopDragOrResizing(control);
control.MouseMove += (sender, e) => MoveControl(container, e);
}
private static void UpdateMouseEdgeProperties(Control control, Point mouseLocationInControl)
{
if (WorkType == MoveOrResize.Move)
{
return;
}
MouseIsInLeftEdge = Math.Abs(mouseLocationInControl.X) <= 2;
MouseIsInRightEdge = Math.Abs(mouseLocationInControl.X - control.Width) <= 2;
MouseIsInTopEdge = Math.Abs(mouseLocationInControl.Y ) <= 2;
MouseIsInBottomEdge = Math.Abs(mouseLocationInControl.Y - control.Height) <= 2;
}
private static void UpdateMouseCursor(Control control)
{
if (WorkType == MoveOrResize.Move)
{
return;
}
if (MouseIsInLeftEdge )
{
if (MouseIsInTopEdge)
{
control.Cursor = Cursors.SizeNWSE;
}
else if (MouseIsInBottomEdge)
{
control.Cursor = Cursors.SizeNESW;
}
else
{
control.Cursor = Cursors.SizeWE;
}
}
else if (MouseIsInRightEdge)
{
if (MouseIsInTopEdge)
{
control.Cursor = Cursors.SizeNESW;
}
else if (MouseIsInBottomEdge)
{
control.Cursor = Cursors.SizeNWSE;
}
else
{
control.Cursor = Cursors.SizeWE;
}
}
else if (MouseIsInTopEdge || MouseIsInBottomEdge)
{
control.Cursor = Cursors.SizeNS;
}
else
{
control.Cursor = Cursors.Default;
}
}
private static void StartMovingOrResizing(Control control, MouseEventArgs e)
{
if (_moving || _resizing)
{
return;
}
if (WorkType!=MoveOrResize.Move &&
(MouseIsInRightEdge || MouseIsInLeftEdge || MouseIsInTopEdge || MouseIsInBottomEdge))
{
_resizing = true;
_currentControlStartSize = control.Size;
}
else if (WorkType!=MoveOrResize.Resize)
{
_moving = true;
control.Cursor = Cursors.Hand;
}
_cursorStartPoint = new Point(e.X, e.Y);
control.Capture = true;
}
private static void MoveControl(Control control, MouseEventArgs e)
{
if (!_resizing && ! _moving)
{
UpdateMouseEdgeProperties(control, new Point(e.X, e.Y));
UpdateMouseCursor(control);
}
if (_resizing)
{
if (MouseIsInLeftEdge)
{
if (MouseIsInTopEdge)
{
control.Width -= (e.X - _cursorStartPoint.X);
control.Left += (e.X - _cursorStartPoint.X);
control.Height -= (e.Y - _cursorStartPoint.Y);
control.Top += (e.Y - _cursorStartPoint.Y);
}
else if (MouseIsInBottomEdge)
{
control.Width -= (e.X - _cursorStartPoint.X);
control.Left += (e.X - _cursorStartPoint.X);
control.Height = (e.Y - _cursorStartPoint.Y) + _currentControlStartSize.Height;
}
else
{
control.Width -= (e.X - _cursorStartPoint.X);
control.Left += (e.X - _cursorStartPoint.X) ;
}
}
else if (MouseIsInRightEdge)
{
if (MouseIsInTopEdge)
{
control.Width = (e.X - _cursorStartPoint.X) + _currentControlStartSize.Width;
control.Height -= (e.Y - _cursorStartPoint.Y);
control.Top += (e.Y - _cursorStartPoint.Y);
}
else if (MouseIsInBottomEdge)
{
control.Width = (e.X - _cursorStartPoint.X) + _currentControlStartSize.Width;
control.Height = (e.Y - _cursorStartPoint.Y) + _currentControlStartSize.Height;
}
else
{
control.Width = (e.X - _cursorStartPoint.X)+_currentControlStartSize.Width;
}
}
else if (MouseIsInTopEdge)
{
control.Height -= (e.Y - _cursorStartPoint.Y);
control.Top += (e.Y - _cursorStartPoint.Y);
}
else if (MouseIsInBottomEdge)
{
control.Height = (e.Y - _cursorStartPoint.Y) + _currentControlStartSize.Height;
}
else
{
StopDragOrResizing(control);
}
}
else if (_moving)
{
_moveIsInterNal = !_moveIsInterNal;
if (!_moveIsInterNal)
{
int x = (e.X - _cursorStartPoint.X) + control.Left;
int y = (e.Y - _cursorStartPoint.Y) + control.Top;
control.Location = new Point(x, y);
}
}
}
private static void StopDragOrResizing(Control control)
{
_resizing = false;
_moving = false;
control.Capture = false;
UpdateMouseCursor(control);
}
#region Save And Load
private static List<Control> GetAllChildControls(Control control, List<Control> list)
{
List<Control> controls = control.Controls.Cast<Control>().ToList();
list.AddRange(controls);
return controls.SelectMany(ctrl => GetAllChildControls(ctrl, list)).ToList();
}
internal static string GetSizeAndPositionOfControlsToString(Control container)
{
List<Control> controls = new List<Control>();
GetAllChildControls(container, controls);
CultureInfo cultureInfo = new CultureInfo("en");
string info = string.Empty;
foreach (Control control in controls)
{
info += control.Name + ":" + control.Left.ToString(cultureInfo) + "," + control.Top.ToString(cultureInfo) + "," +
control.Width.ToString(cultureInfo) + "," + control.Height.ToString(cultureInfo) + "*";
}
return info;
}
internal static void SetSizeAndPositionOfControlsFromString(Control container, string controlsInfoStr)
{
List<Control> controls = new List<Control>();
GetAllChildControls(container, controls);
string[] controlsInfo = controlsInfoStr.Split(new []{"*"},StringSplitOptions.RemoveEmptyEntries );
Dictionary<string, string> controlsInfoDictionary = new Dictionary<string, string>();
foreach (string controlInfo in controlsInfo)
{
string[] info = controlInfo.Split(new [] { ":" }, StringSplitOptions.RemoveEmptyEntries);
controlsInfoDictionary.Add(info[0], info[1]);
}
foreach (Control control in controls)
{
string propertiesStr;
controlsInfoDictionary.TryGetValue(control.Name, out propertiesStr);
string[] properties = propertiesStr.Split(new [] { "," }, StringSplitOptions.RemoveEmptyEntries);
if (properties.Length == 4)
{
control.Left = int.Parse(properties[0]);
control.Top = int.Parse(properties[1]);
control.Width = int.Parse(properties[2]);
control.Height = int.Parse(properties[3]);
}
}
}
#endregion
}
}

You can increase the values of edge tolerance to 5 or more:
MouseIsInLeftEdge = Math.Abs(mouseLocationInControl.X) <= 5;
MouseIsInRightEdge = Math.Abs(mouseLocationInControl.X - control.Width) <= 5;
MouseIsInTopEdge = Math.Abs(mouseLocationInControl.Y ) <= 5;
MouseIsInBottomEdge = Math.Abs(mouseLocationInControl.Y - control.Height) <= 5;

There is a tiny error in the Init routine:
internal static void Init(Control control, Control container)
{
_moving = false;
_resizing = false;
_moveIsInterNal = false;
_cursorStartPoint = Point.Empty;
MouseIsInLeftEdge = false;
MouseIsInLeftEdge = false;
MouseIsInRightEdge = false;
MouseIsInTopEdge = false;
MouseIsInBottomEdge = false;
WorkType = MoveOrResize.MoveAndResize;
control.MouseDown += (sender, e) => StartMovingOrResizing(control, e);
control.MouseUp += (sender, e) => StopDragOrResizing(control);
//control.MouseMove += (sender, e) => MoveControl(container, e); // wrong!!
control.MouseMove += (sender, e) => MoveControl(control, e); // right!
}

Related

How can i keep adding images to pictureBox1 when cropping and adding items to the listBox1 if the flag saveRectangles is false?

This is a link to the winforms project from my onedrive:
https://1drv.ms/u/s!AmVw2eqP6FhB4h01pNXIMlqdZJk_?e=wSGaRx
using Newtonsoft.Json;
using Ookii.Dialogs.WinForms;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.ProgressBar;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
namespace Image_Crop
{
public partial class Form1 : Form
{
Rectangle rect;
int pixelsCounter = 0;
Color SelectedColor = Color.LightGreen;
List<DrawingRectangle> DrawingRects = new List<DrawingRectangle>();
Bitmap rectImage;
int saveRectanglesCounter = 1;
bool drawBorder = true;
bool clearRectangles = true;
bool saveRectangles = true;
//bool drawnNewRectangle = false; // To think how to work with this flag bool variable
// where to use it how and how to solce this problem !!!!!
string rectangleName;
Dictionary<string, string> FileList = new Dictionary<string, string>();
string selectedPath;
int x, y;
private bool crop = false;
public Form1()
{
InitializeComponent();
textBox1.Text = Properties.Settings.Default.ImageToCropFolder;
textBox2.Text = Properties.Settings.Default.CroppedImagesFolder;
selectedPath = textBox2.Text;
if (textBox1.Text != "")
{
Bitmap bmp = new Bitmap(Image.FromFile(textBox1.Text),
pictureBox2.Width, pictureBox2.Height);
pictureBox2.Image = bmp;
}
checkBoxDrawBorder.Checked = true;
checkBoxClearRectangles.Checked = true;
checkBoxSaveRectangles.Checked = true;
if (selectedPath != "" && selectedPath != null)
{
if (System.IO.File.Exists(Path.Combine(selectedPath, "rectangles.txt")))
{
string g = System.IO.File.ReadAllText(Path.Combine(selectedPath, "rectangles.txt"));
g = g.Remove(0, 32);
FileList = JsonConvert.DeserializeObject<Dictionary<string, string>>(g);
listBox1.DataSource = FileList.Keys.ToList();
label2.Text = listBox1.Items.Count.ToString();
listBox1.SelectedIndex = listBox1.Items.Count - 1;
}
else
{
label2.Text = "0";
}
}
else
{
label2.Text = "0";
}
if ((selectedPath != "" && selectedPath != null) && textBox1.Text != "")
{
crop = true;
}
else
{
crop = false;
}
}
public class DrawingRectangle
{
public Rectangle Rect => new Rectangle(Location, Size);
public Size Size { get; set; }
public Point Location { get; set; }
public Control Owner { get; set; }
public Point StartPosition { get; set; }
public Color DrawingcColor { get; set; } = Color.LightGreen;
public float PenSize { get; set; } = 3f;
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void pictureBox2_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left || crop == false) return;
x = 0;
y = 0;
if (pictureBox2.Image != null && selectedPath != null)
{
if ((x >= 0 && x <= pictureBox2.Image.Size.Width) && (y >= 0 && y <= pictureBox2.Image.Size.Height))
{
DrawingRects.Add(new DrawingRectangle()
{
Location = e.Location,
Size = Size.Empty,
StartPosition = e.Location,
Owner = (Control)sender,
DrawingcColor = SelectedColor
});
}
}
}
private void pictureBox2_MouseMove(object sender, MouseEventArgs e)
{
int X = e.X;
int Y = e.Y;
if (e.Button != MouseButtons.Left || crop == false) return;
if ((X >= 0 && X <= pictureBox2.Width) && (Y >= 0 && Y <= pictureBox2.Height))
{
if (pictureBox2.Image != null && selectedPath != null && DrawingRects.Count > 0)
{
if ((x >= 0 && x <= pictureBox2.Image.Size.Width) && (y >= 0 && y <= pictureBox2.Image.Size.Height))
{
x = e.X;
y = e.Y;
var dr = DrawingRects[DrawingRects.Count - 1];
if (e.Y < dr.StartPosition.Y) { dr.Location = new Point(dr.Rect.Location.X, e.Y); }
if (e.X < dr.StartPosition.X) { dr.Location = new Point(e.X, dr.Rect.Location.Y); }
dr.Size = new Size(Math.Abs(dr.StartPosition.X - e.X), Math.Abs(dr.StartPosition.Y - e.Y));
pictureBox2.Invalidate();
}
}
}
}
int count = 0;
private void pictureBox2_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left || crop == false) return;
if (DrawingRects.Count > 0 && pictureBox2.Image != null && selectedPath != "")
{
if ((x >= 0 && x <= pictureBox2.Image.Size.Width) && (y >= 0 && y <= pictureBox2.Image.Size.Height))
{
var dr = DrawingRects.Last();
if (dr.Rect.Width > 0 && dr.Rect.Height > 0)
{
rectImage = cropAtRect((Bitmap)pictureBox2.Image, dr.Rect);
if (saveRectangles)
{
count++;
rectangleName = GetNextName(Path.Combine(selectedPath, "Rectangle"), ".bmp");
FileList.Add($"{dr.Location}, {dr.Size}", rectangleName);
string json = JsonConvert.SerializeObject(
FileList,
Formatting.Indented
);
using (StreamWriter sw = new StreamWriter(Path.Combine(selectedPath, "rectangles.txt"), false))
{
sw.WriteLine("Total number of rectangles: " + count + Environment.NewLine);
sw.Write(json);
sw.Close();
}
rectImage.Save(rectangleName);
saveRectanglesCounter++;
}
pixelsCounter = rect.Width * rect.Height;
pictureBox1.Invalidate();
listBox1.DataSource = FileList.Keys.ToList();
listBox1.SelectedIndex = listBox1.Items.Count - 1;
pictureBox2.Focus();
Graphics g = Graphics.FromImage(this.pictureBox1.Image);
g.Clear(this.pictureBox1.BackColor);
}
}
else
{
if (clearRectangles)
{
DrawingRects.Clear();
pictureBox2.Invalidate();
}
x = 0;
y = 0;
}
}
}
string GetNextName(string baseName, string extension)
{
int counter = 1;
string nextName = baseName + counter + extension;
while (System.IO.File.Exists(nextName))
{
counter++;
nextName = baseName + counter + extension;
}
return nextName;
}
private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
if (drawBorder)
{
ControlPaint.DrawBorder(e.Graphics, pictureBox2.ClientRectangle, Color.Red, ButtonBorderStyle.Solid);
}
if (pictureBox2.Image != null && selectedPath != null && DrawingRects.Count > 0)
{
DrawShapes(e.Graphics);
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (drawBorder)
{
ControlPaint.DrawBorder(e.Graphics, pictureBox1.ClientRectangle, Color.Red, ButtonBorderStyle.Solid);
}
if (rectImage != null && DrawingRects.Count > 0)
{
var dr = DrawingRects.Last();
e.Graphics.DrawImage(rectImage, dr.Rect);
if (clearRectangles)
{
DrawingRects.Clear();
pictureBox2.Invalidate();
}
}
}
private void DrawShapes(Graphics g)
{
if (DrawingRects.Count == 0) return;
g.SmoothingMode = SmoothingMode.AntiAlias;
foreach (var dr in DrawingRects)
{
if (dr.Rect.Width > 0 && dr.Rect.Height > 0)
{
using (Pen pen = new Pen(dr.DrawingcColor, dr.PenSize))
{
g.DrawRectangle(pen, dr.Rect);
};
}
}
}
public Bitmap cropAtRect(Bitmap b, Rectangle r)
{
Bitmap nb = new Bitmap(r.Width, r.Height);
using (Graphics g = Graphics.FromImage(nb))
{
g.DrawImage(b, -r.X, -r.Y);
return nb;
}
}
private void checkBoxDrawBorder_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxDrawBorder.Checked)
{
drawBorder = true;
}
else
{
drawBorder = false;
}
pictureBox1.Invalidate();
pictureBox2.Invalidate();
}
private void checkBoxClearRectangles_CheckedChanged(object sender, EventArgs e)
{
if (checkBoxClearRectangles.Checked)
{
clearRectangles = true;
}
else
{
clearRectangles = false;
}
pictureBox2.Invalidate();
}
private void checkBoxSaveRectangles_CheckedChanged(object sender, EventArgs e)
{
if(checkBoxSaveRectangles.Checked)
{
saveRectangles = true;
}
else
{
saveRectangles = false;
}
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
var item = ((ListBox)sender).SelectedItem;
var val = FileList[(string)item];
pictureBox1.Image = System.Drawing.Image.FromFile(val);
}
private void button1_Click(object sender, EventArgs e)
{
VistaOpenFileDialog dialog = new VistaOpenFileDialog();
{
dialog.Filter = "Images (*.jpg, *.bmp, *.gif)|*.jpg;*.bmp;*.gif";
};
if (dialog.ShowDialog() == DialogResult.OK)
{
textBox1.Text = dialog.FileName;
Properties.Settings.Default.ImageToCropFolder = dialog.FileName;
Properties.Settings.Default.Save();
Bitmap bmp = new Bitmap(Image.FromFile(dialog.FileName),
pictureBox2.Width, pictureBox2.Height);
pictureBox2.Image = bmp;
if(textBox1.Text != "" && textBox2.Text != "")
{
crop = true;
}
}
}
private void button2_Click(object sender, EventArgs e)
{
VistaFolderBrowserDialog dialog = new VistaFolderBrowserDialog();
if (dialog.ShowDialog() == DialogResult.OK)
{
textBox2.Text = dialog.SelectedPath;
selectedPath = dialog.SelectedPath;
Properties.Settings.Default.CroppedImagesFolder = selectedPath;
Properties.Settings.Default.Save();
if (textBox1.Text != "" && textBox2.Text != "")
{
crop = true;
}
}
}
}
}
The problem is in the mouse up event i can't find the logic with the saving flag saveRectangles.
this is the saving part in the mouse up
if (saveRectangles)
{
count++;
rectangleName = GetNextName(Path.Combine(selectedPath, "Rectangle"), ".bmp");
FileList.Add($"{dr.Location}, {dr.Size}", rectangleName);
string json = JsonConvert.SerializeObject(
FileList,
Formatting.Indented
);
using (StreamWriter sw = new StreamWriter(Path.Combine(selectedPath, "rectangles.txt"), false))
{
sw.WriteLine("Total number of rectangles: " + count + Environment.NewLine);
sw.Write(json);
sw.Close();
}
rectImage.Save(rectangleName);
saveRectanglesCounter++;
}
if the checkBox checkBoxSaveRectangles is not checked when running the application and the folder in textBox1 is empty there are no saved images yet on the hard disk then it will throw exception at the line 201 in the MouseUp event :
Graphics g = Graphics.FromImage(this.pictureBox1.Image);
because the image in pictureBox1 is null.
but in that case i still want it to crop images and add the images information as items to the listBox just not to save it to the hard disk.
i think that rectImage variable is also used in the pictureBox1 paint event.
anyway the goal is to be able to keep cropping even if the saving checkbox not checked and the there are no saved images in the cropped images folder.

C# Windows Form snake

I have this simple snake game, my problem is that the tails wont add when it reaches three tails.
namespace Snake
{
public partial class Form1 : Form
{
bool left = false, right = false;
bool top = false, down = false;
PictureBox pic = new PictureBox();
List<PictureBox> tails = new List<PictureBox>();
int score = 0;
public Form1()
{
InitializeComponent();
}
private void Form1_KeyPress(object sender, KeyPressEventArgs e)
{
if (((e.KeyChar.ToString() == "a") || (e.KeyChar.ToString() == "A"))&&(right == false))
{
right = false;
top = false;
down = false;
left = true;
}
else if (((e.KeyChar.ToString() == "d") || (e.KeyChar.ToString() == "D"))&& (left == false))
{
top = false;
down = false;
left = false;
right = true;
}
else if (((e.KeyChar.ToString() == "w") || (e.KeyChar.ToString() == "W"))&& (down == false))
{
down = false;
left = false;
right = false;
top = true;
}
else if (((e.KeyChar.ToString() == "s") || (e.KeyChar.ToString() == "S"))&& (top == false))
{
top = false;
left = false;
right = false;
down = true;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
//ticks every 1 sec
if (pic.Location == head.Location)
{
score++;
spawnFood();
tails.Add(addTails());
}
sortLocation();
if (right == true)
{
int r = head.Location.X + head.Height;
head.Location = new Point(r, head.Location.Y);
}
else if(left == true)
{
int l = head.Location.X - head.Height;
head.Location = new Point(l, head.Location.Y);
}
else if (top == true)
{
int t = head.Location.Y - head.Height;
head.Location = new Point(head.Location.X, t);
}
else if (down == true)
{
int d = head.Location.Y + head.Height;
head.Location = new Point(head.Location.X,d);
}
txtScore.Text = score.ToString();
}
private void sortLocation()
{
if (tails.Count == 0)
{
}
else
{
for (int i = 1; i < tails.Count; i++)
{
tails[i].Location = tails[i-1].Location;
}
tails[0].Location = head.Location;
}
}
private PictureBox addTails()
{
PictureBox tail = new PictureBox();
tail.Name = "tail" + score.ToString();
tail.BackColor = Color.Black;
tail.Width = 10;
tail.Height = 10;
this.Controls.Add(tail);
return tail;
}
private void spawnFood()
{
Random rnd = new Random();
int rndLocationX = rnd.Next(10, 50);
int rndLocationY = rnd.Next(10, 50);
pic.BackColor = Color.Red;
pic.Height = 10;
pic.Width = 10;
this.Controls.Add(pic);
if (rndLocationX >= 500)
{
rndLocationX -= 10;
}
if (rndLocationY >= 500)
{
rndLocationY -= 10;
}
pic.Location = new Point(rndLocationX*10,rndLocationY*10);
}
private void Form1_Load(object sender, EventArgs e)
{
timer1.Start();
spawnFood();
}
}
}
for (int i = 1; i < tails.Count; i++)
{
tails[i].Location = tails[i+1].Location;
}
tails[0].Location = head.Location;
Are your tails there just stacked so to speak? That's another thought I might be thinking. I changed the tails[i-1] to tails [i+1]. Check if that does the trick.

autocomplete textbox in windows application with not only prefix search [duplicate]

I have a textbox that does autocompletion like so:
txtName.AutoCompleteMode = AutoCompleteMode.Suggest;
txtName.AutoCompleteSource = AutoCompleteSource.CustomSource;
txtName.AutoCompleteCustomSource = namesCollection;
It works, but only at the beginning of a textbox. I'd like autocomplete to kick in for any word the user is entering, at any position in the textbox.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace TubeUploader
{
public class AutoCompleteTextBox : TextBox
{
private ListBox _listBox;
private bool _isAdded;
private String[] _values;
private String _formerValue = String.Empty;
public AutoCompleteTextBox()
{
InitializeComponent();
ResetListBox();
}
private void InitializeComponent()
{
_listBox = new ListBox();
KeyDown += this_KeyDown;
KeyUp += this_KeyUp;
}
private void ShowListBox()
{
if (!_isAdded)
{
Parent.Controls.Add(_listBox);
_listBox.Left = Left;
_listBox.Top = Top + Height;
_isAdded = true;
}
_listBox.Visible = true;
_listBox.BringToFront();
}
private void ResetListBox()
{
_listBox.Visible = false;
}
private void this_KeyUp(object sender, KeyEventArgs e)
{
UpdateListBox();
}
private void this_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Tab:
{
if (_listBox.Visible)
{
InsertWord((String)_listBox.SelectedItem);
ResetListBox();
_formerValue = Text;
}
break;
}
case Keys.Down:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex < _listBox.Items.Count - 1))
_listBox.SelectedIndex++;
break;
}
case Keys.Up:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex > 0))
_listBox.SelectedIndex--;
break;
}
}
}
protected override bool IsInputKey(Keys keyData)
{
switch (keyData)
{
case Keys.Tab:
return true;
default:
return base.IsInputKey(keyData);
}
}
private void UpdateListBox()
{
if (Text == _formerValue) return;
_formerValue = Text;
String word = GetWord();
if (_values != null && word.Length > 0)
{
String[] matches = Array.FindAll(_values,
x => (x.StartsWith(word, StringComparison.OrdinalIgnoreCase) && !SelectedValues.Contains(x)));
if (matches.Length > 0)
{
ShowListBox();
_listBox.Items.Clear();
Array.ForEach(matches, x => _listBox.Items.Add(x));
_listBox.SelectedIndex = 0;
_listBox.Height = 0;
_listBox.Width = 0;
Focus();
using (Graphics graphics = _listBox.CreateGraphics())
{
for (int i = 0; i < _listBox.Items.Count; i++)
{
_listBox.Height += _listBox.GetItemHeight(i);
// it item width is larger than the current one
// set it to the new max item width
// GetItemRectangle does not work for me
// we add a little extra space by using '_'
int itemWidth = (int)graphics.MeasureString(((String)_listBox.Items[i]) + "_", _listBox.Font).Width;
_listBox.Width = (_listBox.Width < itemWidth) ? itemWidth : _listBox.Width;
}
}
}
else
{
ResetListBox();
}
}
else
{
ResetListBox();
}
}
private String GetWord()
{
String text = Text;
int pos = SelectionStart;
int posStart = text.LastIndexOf(' ', (pos < 1) ? 0 : pos - 1);
posStart = (posStart == -1) ? 0 : posStart + 1;
int posEnd = text.IndexOf(' ', pos);
posEnd = (posEnd == -1) ? text.Length : posEnd;
int length = ((posEnd - posStart) < 0) ? 0 : posEnd - posStart;
return text.Substring(posStart, length);
}
private void InsertWord(String newTag)
{
String text = Text;
int pos = SelectionStart;
int posStart = text.LastIndexOf(' ', (pos < 1) ? 0 : pos - 1);
posStart = (posStart == -1) ? 0 : posStart + 1;
int posEnd = text.IndexOf(' ', pos);
String firstPart = text.Substring(0, posStart) + newTag;
String updatedText = firstPart + ((posEnd == -1) ? "" : text.Substring(posEnd, text.Length - posEnd));
Text = updatedText;
SelectionStart = firstPart.Length;
}
public String[] Values
{
get
{
return _values;
}
set
{
_values = value;
}
}
public List<String> SelectedValues
{
get
{
String[] result = Text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
return new List<String>(result);
}
}
}
}
Sample Usage
using System;
using System.Windows.Forms;
namespace AutoComplete
{
public partial class TestForm : Form
{
private readonly String[] _values = { "one", "two", "three", "tree", "four", "fivee" };
public TestForm()
{
InitializeComponent();
// AutoComplete is our special textbox control on the form
AutoComplete.Values = _values;
}
}
}
I made a few changes to the solution proposed by #PaRiMaL RaJ because the list box was not being displayed when the text box was inside a UserControl that was not tall enough. Basically, instead of adding the list box to the parent of the text box, I added to the form and I calculate the absolute position in the form.
public class AutoCompleteTextBox : TextBox
{
private ListBox _listBox;
private bool _isAdded;
private String[] _values;
private String _formerValue = String.Empty;
public AutoCompleteTextBox()
{
InitializeComponent();
ResetListBox();
}
private void InitializeComponent()
{
_listBox = new ListBox();
this.KeyDown += this_KeyDown;
this.KeyUp += this_KeyUp;
}
private void ShowListBox()
{
if (!_isAdded)
{
Form parentForm = this.FindForm(); // new line added
parentForm.Controls.Add(_listBox); // adds it to the form
Point positionOnForm = parentForm.PointToClient(this.Parent.PointToScreen(this.Location)); // absolute position in the form
_listBox.Left = positionOnForm.X;
_listBox.Top = positionOnForm.Y + Height;
_isAdded = true;
}
_listBox.Visible = true;
_listBox.BringToFront();
}
private void ResetListBox()
{
_listBox.Visible = false;
}
private void this_KeyUp(object sender, KeyEventArgs e)
{
UpdateListBox();
}
private void this_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Enter:
case Keys.Tab:
{
if (_listBox.Visible)
{
Text = _listBox.SelectedItem.ToString();
ResetListBox();
_formerValue = Text;
this.Select(this.Text.Length, 0);
e.Handled = true;
}
break;
}
case Keys.Down:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex < _listBox.Items.Count - 1))
_listBox.SelectedIndex++;
e.Handled = true;
break;
}
case Keys.Up:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex > 0))
_listBox.SelectedIndex--;
e.Handled = true;
break;
}
}
}
protected override bool IsInputKey(Keys keyData)
{
switch (keyData)
{
case Keys.Tab:
if (_listBox.Visible)
return true;
else
return false;
default:
return base.IsInputKey(keyData);
}
}
private void UpdateListBox()
{
if (Text == _formerValue)
return;
_formerValue = this.Text;
string word = this.Text;
if (_values != null && word.Length > 0)
{
string[] matches = Array.FindAll(_values,
x => (x.ToLower().Contains(word.ToLower())));
if (matches.Length > 0)
{
ShowListBox();
_listBox.BeginUpdate();
_listBox.Items.Clear();
Array.ForEach(matches, x => _listBox.Items.Add(x));
_listBox.SelectedIndex = 0;
_listBox.Height = 0;
_listBox.Width = 0;
Focus();
using (Graphics graphics = _listBox.CreateGraphics())
{
for (int i = 0; i < _listBox.Items.Count; i++)
{
if (i < 20)
_listBox.Height += _listBox.GetItemHeight(i);
// it item width is larger than the current one
// set it to the new max item width
// GetItemRectangle does not work for me
// we add a little extra space by using '_'
int itemWidth = (int)graphics.MeasureString(((string)_listBox.Items[i]) + "_", _listBox.Font).Width;
_listBox.Width = (_listBox.Width < itemWidth) ? itemWidth : this.Width; ;
}
}
_listBox.EndUpdate();
}
else
{
ResetListBox();
}
}
else
{
ResetListBox();
}
}
public String[] Values
{
get
{
return _values;
}
set
{
_values = value;
}
}
public List<String> SelectedValues
{
get
{
String[] result = Text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
return new List<String>(result);
}
}
}
The other solutions didn't work for me in a multiline environment to my needs, so I've added to #Francisco Goldenstein's answer to enable this. What I needed was to autocomplete any 'word' in the TextBox and in any position/line. After minimal testing, this class seems to work well enough for me in a multiline TextBox. Hope it helps someone.
Main changes are in UpdateListBox() and this_KeyDown(), to deal with the 'current' word, i.e. the one just before the caret position, rather than the entire textbox contents.
Change the definition of separators in UpdateListBox() to suit your needs.
using System;
using System.Drawing;
using System.Windows.Forms;
class MultiLineAutoCompleteTextBox : TextBox
{
private ListBox _listBox;
private bool _isAdded;
private String[] _values;
private String _formerValue = String.Empty;
private int _prevBreak;
private int _nextBreak;
private int _wordLen;
public MultiLineAutoCompleteTextBox()
{
InitializeComponent();
ResetListBox();
}
private void InitializeComponent()
{
_listBox = new ListBox();
KeyDown += this_KeyDown;
KeyUp += this_KeyUp;
}
private void ShowListBox()
{
if (!_isAdded)
{
Form parentForm = FindForm();
if (parentForm == null) return;
parentForm.Controls.Add(_listBox);
Point positionOnForm = parentForm.PointToClient(Parent.PointToScreen(Location));
_listBox.Left = positionOnForm.X;
_listBox.Top = positionOnForm.Y + Height;
_isAdded = true;
}
_listBox.Visible = true;
_listBox.BringToFront();
}
private void ResetListBox()
{
_listBox.Visible = false;
}
private void this_KeyUp(object sender, KeyEventArgs e)
{
UpdateListBox();
}
private void this_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Enter:
case Keys.Tab:
case Keys.Space:
{
if (_listBox.Visible)
{
Text = Text.Remove(_prevBreak == 0 ? 0 : _prevBreak + 1, _prevBreak == 0 ? _wordLen + 1 : _wordLen);
Text = Text.Insert(_prevBreak == 0 ? 0 : _prevBreak + 1, _listBox.SelectedItem.ToString());
ResetListBox();
_formerValue = Text;
Select(Text.Length, 0);
e.Handled = true;
}
break;
}
case Keys.Down:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex < _listBox.Items.Count - 1))
_listBox.SelectedIndex++;
e.Handled = true;
break;
}
case Keys.Up:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex > 0))
_listBox.SelectedIndex--;
e.Handled = true;
break;
}
}
}
protected override bool IsInputKey(Keys keyData)
{
switch (keyData)
{
case Keys.Tab:
if (_listBox.Visible)
return true;
else
return false;
default:
return base.IsInputKey(keyData);
}
}
private void UpdateListBox()
{
if (Text == _formerValue) return;
if (Text.Length == 0)
{
_listBox.Visible = false;
return;
}
_formerValue = Text;
var separators = new[] { '|', '[', ']', '\r', '\n', ' ', '\t' };
_prevBreak = Text.LastIndexOfAny(separators, CaretIndex > 0 ? CaretIndex - 1 : 0);
if (_prevBreak < 1) _prevBreak = 0;
_nextBreak = Text.IndexOfAny(separators, _prevBreak + 1);
if (_nextBreak == -1) _nextBreak = CaretIndex;
_wordLen = _nextBreak - _prevBreak - 1;
if (_wordLen < 1) return;
string word = Text.Substring(_prevBreak + 1, _wordLen);
if (_values != null && word.Length > 0)
{
string[] matches = Array.FindAll(_values,
x => (x.ToLower().Contains(word.ToLower())));
if (matches.Length > 0)
{
ShowListBox();
_listBox.BeginUpdate();
_listBox.Items.Clear();
Array.ForEach(matches, x => _listBox.Items.Add(x));
_listBox.SelectedIndex = 0;
_listBox.Height = 0;
_listBox.Width = 0;
Focus();
using (Graphics graphics = _listBox.CreateGraphics())
{
for (int i = 0; i < _listBox.Items.Count; i++)
{
if (i < 20)
_listBox.Height += _listBox.GetItemHeight(i);
// it item width is larger than the current one
// set it to the new max item width
// GetItemRectangle does not work for me
// we add a little extra space by using '_'
int itemWidth = (int)graphics.MeasureString(((string)_listBox.Items[i]) + "_", _listBox.Font).Width;
_listBox.Width = (_listBox.Width < itemWidth) ? itemWidth : Width; ;
}
}
_listBox.EndUpdate();
}
else
{
ResetListBox();
}
}
else
{
ResetListBox();
}
}
public int CaretIndex => SelectionStart;
public String[] Values
{
get
{
return _values;
}
set
{
_values = value;
}
}
}

How to limit dragging an image inside a Panel?

Good evening. I'm currently working on an interactive map editor. I'm trying to keep the map image from being dragged out of view by using this code
if (currentPosition.X > 0)
currentPosition.X = 0;
else if (currentPosition.X < -mapSize.Width + Size.Width )
currentPosition.X = -mapSize.Width + Size.Width ;
if (currentPosition.Y > 0)
currentPosition.Y = 0;
else if (currentPosition.Y < -mapSize.Height + Size.Height)
currentPosition.Y = -mapSize.Height + Size.Height;
However I can't drag it to the limits Size-mapSize.
The currentPosition corresponds to the upper left location of the image. Here's the full code of the class
namespace GameMaps
{
public class MapPanel : Panel
{
private Bitmap shownMap = null;
private Point mapLocation;
private Size mapSize;
private bool dragging;
private Point currentPosition;
//private Point newPosition;
private Point initialPosition;
private Point initialMousePosition;
private Form1 parentForm;
public MapPanel(Form1 parentForm) : base()
{
this.DoubleBuffered = true;
this.parentForm = parentForm;
}
public MapPanel(Panel targetPanel) : this(new Form1())
{
}
public void ShowMap(Map map)
{
if (map == null) return;
shownMap = map.MapTexture;
mapLocation = new Point(0, 0);
mapSize = shownMap.Size;
currentPosition = new Point();
initialPosition = new Point();
initialMousePosition = new Point(); //mapLocation;
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.PageUnit = GraphicsUnit.Pixel;
e.Graphics.PageScale = 1.0F;
// base.OnPaint(e);
if (shownMap == null)
{
base.OnPaint(e);
return;
}
e.Graphics.DrawImage(shownMap, currentPosition.X, currentPosition.Y);
}
protected override void OnMouseDown(MouseEventArgs e)
{
// base.OnMouseDown(e);
if (shownMap == null)
{
base.OnMouseDown(e);
return;
}
if (dragging)
{
dragging = false;
return;
}
else dragging = true;
initialPosition.X = currentPosition.X;
initialPosition.Y = currentPosition.Y;
initialMousePosition = e.Location;
}
protected override void OnMouseMove(MouseEventArgs e)
{
/*parentForm.label7.Text = currentPosition.X.ToString();
parentForm.label8.Text = currentPosition.Y.ToString();
parentForm.label9.Text = mapSize.Width.ToString();
parentForm.label10.Text = mapSize.Height.ToString();
parentForm.label11.Text = Size.Width.ToString();
parentForm.label12.Text = Size.Width.ToString();*/
//base.OnMouseMove(e);
if (!dragging) return;
currentPosition.X = e.X - initialMousePosition.X + initialPosition.X;
currentPosition.Y = e.Y - initialMousePosition.Y + initialPosition.Y;
if (currentPosition.X > 0)
currentPosition.X = 0;
else if (currentPosition.X < -mapSize.Width + Size.Width )
currentPosition.X = -mapSize.Width + Size.Width ;
if (currentPosition.Y > 0)
currentPosition.Y = 0;
else if (currentPosition.Y < -mapSize.Height + Size.Height)
currentPosition.Y = -mapSize.Height + Size.Height;
//if (currentPosition.X + mapSize.Width < this.Size.Width)
// currentPosition.X = this.Size.Width - mapSize.Width;
Invalidate();
// (e.X - initialPosition.X + xinit, e.Y - init_loc.Y + yinit);
}
protected override void OnMouseLeave(EventArgs e)
{
// base.OnMouseLeave(e);
dragging = false;
// F*! if I hold the mouse down this does not work
}
protected override void OnMouseUp(MouseEventArgs e)
{
//base.OnMouseUp(e);
dragging = false;
}
}
}
Well, it seems there was no bad logic except for the fact that I was using the Panel's Size instead of its DisplayRectangle (Size.Width == DisplayRectangle.Width-2). The whole problem was that the images I was using had been exported from Photoshop and had a resolution of 72dpi instead of 96dpi: Same Size, but on using DrawImage they were scaled up. Here's my solution. There are still this to correct but this is the solution regarding my original question. Also some variable names were changed.
namespace GameMaps
{
public class MapPanel : Panel
{
private Map sourceMap;
private Bitmap mapTexture;
private Size mapSize;
private Point mapPosition;
private float dpiX, dpiY;
private Point initialPosition;
private Point initialMousePosition;
private bool dragging;
/* Constructors */
public MapPanel() : base()
{
this.DoubleBuffered = true;
this.sourceMap = null;
this.dpiX = 96.0F; this.dpiY = 96.0F; // For now, this Application won't be DPI aware
}
public MapPanel(Panel targetPanel) : this()
{
// TO DO
}
/* Show a Map */
public void ShowMap(Map map)
{
if (map == null)
return;
sourceMap = map;
mapTexture = new Bitmap(map.MapTexture);
mapTexture.SetResolution(dpiX, dpiY);
mapSize = mapTexture.Size; // ?
mapPosition = new Point(0, 0);
}
protected override void OnPaint(PaintEventArgs e)
{
// base.OnPaint(e);
e.Graphics.PageUnit = GraphicsUnit.Pixel; // ?
e.Graphics.PageScale = 1.0F; // ?
if (sourceMap == null)
{
base.OnPaint(e);
return;
}
e.Graphics.DrawImage(mapTexture, mapPosition.X, mapPosition.Y);
}
protected override void OnMouseDown(MouseEventArgs e)
{
// base.OnMouseDown(e);
if (sourceMap == null)
{
base.OnMouseDown(e);
return;
}
if (dragging)
{
dragging = false;
return;
}
else dragging = true;
initialPosition.X = mapPosition.X;
initialPosition.Y = mapPosition.Y;
initialMousePosition.X = e.Location.X;
initialMousePosition.Y = e.Location.Y;
}
protected override void OnMouseMove(MouseEventArgs e)
{
//base.OnMouseMove(e);
if (!dragging) return;
mapPosition.X = e.X - initialMousePosition.X + initialPosition.X;
mapPosition.Y = e.Y - initialMousePosition.Y + initialPosition.Y;
if (mapPosition.X > 0)
mapPosition.X = 0;
else if (mapPosition.X < DisplayRectangle.Width - mapSize.Width)
mapPosition.X = DisplayRectangle.Width - mapSize.Width;
if (mapPosition.Y > 0)
mapPosition.Y = 0;
else if (mapPosition.Y < DisplayRectangle.Height - mapSize.Height)
mapPosition.Y = DisplayRectangle.Height - mapSize.Height;
Invalidate(); // Force Repaint
}
protected override void OnMouseLeave(EventArgs e)
{
// base.OnMouseLeave(e);
dragging = false;
// TO DO: Correct this
}
protected override void OnMouseUp(MouseEventArgs e)
{
//base.OnMouseUp(e);
dragging = false;
}
}
}

WinForms | C# | AutoComplete in the Middle of a Textbox?

I have a textbox that does autocompletion like so:
txtName.AutoCompleteMode = AutoCompleteMode.Suggest;
txtName.AutoCompleteSource = AutoCompleteSource.CustomSource;
txtName.AutoCompleteCustomSource = namesCollection;
It works, but only at the beginning of a textbox. I'd like autocomplete to kick in for any word the user is entering, at any position in the textbox.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace TubeUploader
{
public class AutoCompleteTextBox : TextBox
{
private ListBox _listBox;
private bool _isAdded;
private String[] _values;
private String _formerValue = String.Empty;
public AutoCompleteTextBox()
{
InitializeComponent();
ResetListBox();
}
private void InitializeComponent()
{
_listBox = new ListBox();
KeyDown += this_KeyDown;
KeyUp += this_KeyUp;
}
private void ShowListBox()
{
if (!_isAdded)
{
Parent.Controls.Add(_listBox);
_listBox.Left = Left;
_listBox.Top = Top + Height;
_isAdded = true;
}
_listBox.Visible = true;
_listBox.BringToFront();
}
private void ResetListBox()
{
_listBox.Visible = false;
}
private void this_KeyUp(object sender, KeyEventArgs e)
{
UpdateListBox();
}
private void this_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Tab:
{
if (_listBox.Visible)
{
InsertWord((String)_listBox.SelectedItem);
ResetListBox();
_formerValue = Text;
}
break;
}
case Keys.Down:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex < _listBox.Items.Count - 1))
_listBox.SelectedIndex++;
break;
}
case Keys.Up:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex > 0))
_listBox.SelectedIndex--;
break;
}
}
}
protected override bool IsInputKey(Keys keyData)
{
switch (keyData)
{
case Keys.Tab:
return true;
default:
return base.IsInputKey(keyData);
}
}
private void UpdateListBox()
{
if (Text == _formerValue) return;
_formerValue = Text;
String word = GetWord();
if (_values != null && word.Length > 0)
{
String[] matches = Array.FindAll(_values,
x => (x.StartsWith(word, StringComparison.OrdinalIgnoreCase) && !SelectedValues.Contains(x)));
if (matches.Length > 0)
{
ShowListBox();
_listBox.Items.Clear();
Array.ForEach(matches, x => _listBox.Items.Add(x));
_listBox.SelectedIndex = 0;
_listBox.Height = 0;
_listBox.Width = 0;
Focus();
using (Graphics graphics = _listBox.CreateGraphics())
{
for (int i = 0; i < _listBox.Items.Count; i++)
{
_listBox.Height += _listBox.GetItemHeight(i);
// it item width is larger than the current one
// set it to the new max item width
// GetItemRectangle does not work for me
// we add a little extra space by using '_'
int itemWidth = (int)graphics.MeasureString(((String)_listBox.Items[i]) + "_", _listBox.Font).Width;
_listBox.Width = (_listBox.Width < itemWidth) ? itemWidth : _listBox.Width;
}
}
}
else
{
ResetListBox();
}
}
else
{
ResetListBox();
}
}
private String GetWord()
{
String text = Text;
int pos = SelectionStart;
int posStart = text.LastIndexOf(' ', (pos < 1) ? 0 : pos - 1);
posStart = (posStart == -1) ? 0 : posStart + 1;
int posEnd = text.IndexOf(' ', pos);
posEnd = (posEnd == -1) ? text.Length : posEnd;
int length = ((posEnd - posStart) < 0) ? 0 : posEnd - posStart;
return text.Substring(posStart, length);
}
private void InsertWord(String newTag)
{
String text = Text;
int pos = SelectionStart;
int posStart = text.LastIndexOf(' ', (pos < 1) ? 0 : pos - 1);
posStart = (posStart == -1) ? 0 : posStart + 1;
int posEnd = text.IndexOf(' ', pos);
String firstPart = text.Substring(0, posStart) + newTag;
String updatedText = firstPart + ((posEnd == -1) ? "" : text.Substring(posEnd, text.Length - posEnd));
Text = updatedText;
SelectionStart = firstPart.Length;
}
public String[] Values
{
get
{
return _values;
}
set
{
_values = value;
}
}
public List<String> SelectedValues
{
get
{
String[] result = Text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
return new List<String>(result);
}
}
}
}
Sample Usage
using System;
using System.Windows.Forms;
namespace AutoComplete
{
public partial class TestForm : Form
{
private readonly String[] _values = { "one", "two", "three", "tree", "four", "fivee" };
public TestForm()
{
InitializeComponent();
// AutoComplete is our special textbox control on the form
AutoComplete.Values = _values;
}
}
}
I made a few changes to the solution proposed by #PaRiMaL RaJ because the list box was not being displayed when the text box was inside a UserControl that was not tall enough. Basically, instead of adding the list box to the parent of the text box, I added to the form and I calculate the absolute position in the form.
public class AutoCompleteTextBox : TextBox
{
private ListBox _listBox;
private bool _isAdded;
private String[] _values;
private String _formerValue = String.Empty;
public AutoCompleteTextBox()
{
InitializeComponent();
ResetListBox();
}
private void InitializeComponent()
{
_listBox = new ListBox();
this.KeyDown += this_KeyDown;
this.KeyUp += this_KeyUp;
}
private void ShowListBox()
{
if (!_isAdded)
{
Form parentForm = this.FindForm(); // new line added
parentForm.Controls.Add(_listBox); // adds it to the form
Point positionOnForm = parentForm.PointToClient(this.Parent.PointToScreen(this.Location)); // absolute position in the form
_listBox.Left = positionOnForm.X;
_listBox.Top = positionOnForm.Y + Height;
_isAdded = true;
}
_listBox.Visible = true;
_listBox.BringToFront();
}
private void ResetListBox()
{
_listBox.Visible = false;
}
private void this_KeyUp(object sender, KeyEventArgs e)
{
UpdateListBox();
}
private void this_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Enter:
case Keys.Tab:
{
if (_listBox.Visible)
{
Text = _listBox.SelectedItem.ToString();
ResetListBox();
_formerValue = Text;
this.Select(this.Text.Length, 0);
e.Handled = true;
}
break;
}
case Keys.Down:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex < _listBox.Items.Count - 1))
_listBox.SelectedIndex++;
e.Handled = true;
break;
}
case Keys.Up:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex > 0))
_listBox.SelectedIndex--;
e.Handled = true;
break;
}
}
}
protected override bool IsInputKey(Keys keyData)
{
switch (keyData)
{
case Keys.Tab:
if (_listBox.Visible)
return true;
else
return false;
default:
return base.IsInputKey(keyData);
}
}
private void UpdateListBox()
{
if (Text == _formerValue)
return;
_formerValue = this.Text;
string word = this.Text;
if (_values != null && word.Length > 0)
{
string[] matches = Array.FindAll(_values,
x => (x.ToLower().Contains(word.ToLower())));
if (matches.Length > 0)
{
ShowListBox();
_listBox.BeginUpdate();
_listBox.Items.Clear();
Array.ForEach(matches, x => _listBox.Items.Add(x));
_listBox.SelectedIndex = 0;
_listBox.Height = 0;
_listBox.Width = 0;
Focus();
using (Graphics graphics = _listBox.CreateGraphics())
{
for (int i = 0; i < _listBox.Items.Count; i++)
{
if (i < 20)
_listBox.Height += _listBox.GetItemHeight(i);
// it item width is larger than the current one
// set it to the new max item width
// GetItemRectangle does not work for me
// we add a little extra space by using '_'
int itemWidth = (int)graphics.MeasureString(((string)_listBox.Items[i]) + "_", _listBox.Font).Width;
_listBox.Width = (_listBox.Width < itemWidth) ? itemWidth : this.Width; ;
}
}
_listBox.EndUpdate();
}
else
{
ResetListBox();
}
}
else
{
ResetListBox();
}
}
public String[] Values
{
get
{
return _values;
}
set
{
_values = value;
}
}
public List<String> SelectedValues
{
get
{
String[] result = Text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
return new List<String>(result);
}
}
}
The other solutions didn't work for me in a multiline environment to my needs, so I've added to #Francisco Goldenstein's answer to enable this. What I needed was to autocomplete any 'word' in the TextBox and in any position/line. After minimal testing, this class seems to work well enough for me in a multiline TextBox. Hope it helps someone.
Main changes are in UpdateListBox() and this_KeyDown(), to deal with the 'current' word, i.e. the one just before the caret position, rather than the entire textbox contents.
Change the definition of separators in UpdateListBox() to suit your needs.
using System;
using System.Drawing;
using System.Windows.Forms;
class MultiLineAutoCompleteTextBox : TextBox
{
private ListBox _listBox;
private bool _isAdded;
private String[] _values;
private String _formerValue = String.Empty;
private int _prevBreak;
private int _nextBreak;
private int _wordLen;
public MultiLineAutoCompleteTextBox()
{
InitializeComponent();
ResetListBox();
}
private void InitializeComponent()
{
_listBox = new ListBox();
KeyDown += this_KeyDown;
KeyUp += this_KeyUp;
}
private void ShowListBox()
{
if (!_isAdded)
{
Form parentForm = FindForm();
if (parentForm == null) return;
parentForm.Controls.Add(_listBox);
Point positionOnForm = parentForm.PointToClient(Parent.PointToScreen(Location));
_listBox.Left = positionOnForm.X;
_listBox.Top = positionOnForm.Y + Height;
_isAdded = true;
}
_listBox.Visible = true;
_listBox.BringToFront();
}
private void ResetListBox()
{
_listBox.Visible = false;
}
private void this_KeyUp(object sender, KeyEventArgs e)
{
UpdateListBox();
}
private void this_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Enter:
case Keys.Tab:
case Keys.Space:
{
if (_listBox.Visible)
{
Text = Text.Remove(_prevBreak == 0 ? 0 : _prevBreak + 1, _prevBreak == 0 ? _wordLen + 1 : _wordLen);
Text = Text.Insert(_prevBreak == 0 ? 0 : _prevBreak + 1, _listBox.SelectedItem.ToString());
ResetListBox();
_formerValue = Text;
Select(Text.Length, 0);
e.Handled = true;
}
break;
}
case Keys.Down:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex < _listBox.Items.Count - 1))
_listBox.SelectedIndex++;
e.Handled = true;
break;
}
case Keys.Up:
{
if ((_listBox.Visible) && (_listBox.SelectedIndex > 0))
_listBox.SelectedIndex--;
e.Handled = true;
break;
}
}
}
protected override bool IsInputKey(Keys keyData)
{
switch (keyData)
{
case Keys.Tab:
if (_listBox.Visible)
return true;
else
return false;
default:
return base.IsInputKey(keyData);
}
}
private void UpdateListBox()
{
if (Text == _formerValue) return;
if (Text.Length == 0)
{
_listBox.Visible = false;
return;
}
_formerValue = Text;
var separators = new[] { '|', '[', ']', '\r', '\n', ' ', '\t' };
_prevBreak = Text.LastIndexOfAny(separators, CaretIndex > 0 ? CaretIndex - 1 : 0);
if (_prevBreak < 1) _prevBreak = 0;
_nextBreak = Text.IndexOfAny(separators, _prevBreak + 1);
if (_nextBreak == -1) _nextBreak = CaretIndex;
_wordLen = _nextBreak - _prevBreak - 1;
if (_wordLen < 1) return;
string word = Text.Substring(_prevBreak + 1, _wordLen);
if (_values != null && word.Length > 0)
{
string[] matches = Array.FindAll(_values,
x => (x.ToLower().Contains(word.ToLower())));
if (matches.Length > 0)
{
ShowListBox();
_listBox.BeginUpdate();
_listBox.Items.Clear();
Array.ForEach(matches, x => _listBox.Items.Add(x));
_listBox.SelectedIndex = 0;
_listBox.Height = 0;
_listBox.Width = 0;
Focus();
using (Graphics graphics = _listBox.CreateGraphics())
{
for (int i = 0; i < _listBox.Items.Count; i++)
{
if (i < 20)
_listBox.Height += _listBox.GetItemHeight(i);
// it item width is larger than the current one
// set it to the new max item width
// GetItemRectangle does not work for me
// we add a little extra space by using '_'
int itemWidth = (int)graphics.MeasureString(((string)_listBox.Items[i]) + "_", _listBox.Font).Width;
_listBox.Width = (_listBox.Width < itemWidth) ? itemWidth : Width; ;
}
}
_listBox.EndUpdate();
}
else
{
ResetListBox();
}
}
else
{
ResetListBox();
}
}
public int CaretIndex => SelectionStart;
public String[] Values
{
get
{
return _values;
}
set
{
_values = value;
}
}
}

Categories