创建了一个自定义智能感知文本框(带有列表框的子文本框)。 如下图所示,当我输入一个字符串时,列表框会弹出,所有字符都很好但是当我在文本框的末尾时,列表框部分可见,无论如何我可以显示整个列表框内容吗?
试过这个" Show control inside user control outside the boundaries of its parent
但是当弹出窗口打开时,文本框失去焦点,我无法再输入任何内容,我的智能感知文本框会根据键入的内容不断提供更好的结果,但在这种情况下,我无法再输入。
FYI试图将pParentControl.Focus()添加到其他文章中定义的show方法中,如下所示,遗漏了什么?
public void Show(Control pParentControl)
{
if (pParentControl == null) return;
// position the popup window
var loc = pParentControl.PointToScreen(new Point(0, pParentControl.Height));
pParentControl.Focus();
m_tsdd.Show(loc);
}
这是完整的代码
class TextBox_AutoComplete : TextBox
{
#region Class Members
List<string> dictionary;
ListBox listbox = new ListBox();
#endregion
private PopupHelper m_popup;
#region Extern functions
[DllImport("user32")]
private extern static int GetCaretPos(out Point p);
#endregion
#region Constructors
public TextBox_AutoComplete() : base()
{
this.Margin = new Padding(0, 0, 0, 0);
this.Multiline = true;
this.Dock = DockStyle.Fill;
this.KeyDown += Textbox_KeyDown;
this.KeyUp += Textbox_KeyUp;
listbox.Parent = this;
listbox.KeyUp += List_OnKeyUp;
listbox.Visible = false;
this.dictionary = new List<string>();
}
#endregion
#region Properties
public List<string> Dictionary
{
get { return this.dictionary; }
set { this.dictionary = value; }
}
#endregion
#region Methods
private static string GetLastString(string s)
{
Regex rgx = new Regex("[^a-zA-Z0-9_.\\[\\]]");
s = rgx.Replace(s, " ");
string[] strArray = s.Split(' ');
return strArray[strArray.Length - 1];
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
Point cp;
GetCaretPos(out cp);
List<string> lstTemp = new List<string>();
List<string> TempFilteredList = new List<string>();
string LastString = GetLastString(this.Text.Substring(0, SelectionStart));
//MessageBox.Show(LastString);
/*seperated them so that column name matches are found first*/
TempFilteredList.AddRange(dictionary.Where(n => n.Replace("[", "").ToUpper().Substring(n.IndexOf(".") > 0 ? n.IndexOf(".") : 0).StartsWith(LastString.ToUpper())
).Select(r => r)
.ToList());
TempFilteredList.AddRange(dictionary.Where(n => n.Replace("[", "").ToUpper().StartsWith(LastString.ToUpper())
|| n.ToUpper().StartsWith(LastString.ToUpper()))
.Select(r => r)
.ToList());
lstTemp = TempFilteredList.Distinct().Select(r => r).ToList();
/*Getting max width*/
int maxWidth = 0, temp = 0;
foreach (var obj in lstTemp)
{
temp = TextRenderer.MeasureText(obj.ToString(), new Font("Arial", 10, FontStyle.Regular)).Width;
if (temp > maxWidth)
{
maxWidth = temp;
}
}
listbox.SetBounds(cp.X + 20, cp.Y + 20, maxWidth, 60);
if (lstTemp.Count != 0 && LastString != "")
{
listbox.DataSource = lstTemp;
// listbox.Show();
if (m_popup == null)
m_popup = new PopupHelper(listbox);
m_popup.Show(this);
}
else if (m_popup != null)
{
//listbox.Hide();
m_popup.Hide();
}
}
protected void Textbox_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Down)
{
if (listbox.Visible == true)
{
listbox.Focus();
}
e.Handled = true;
}
else if (e.KeyCode == Keys.Escape)
{
listbox.Visible = false;
e.Handled = true;
}
}
protected void Textbox_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Space && listbox.Visible == true)
{
listbox.Focus();
List_OnKeyUp(listbox, new KeyEventArgs(Keys.Space));
e.Handled = true;
}
if (e.KeyCode == Keys.Down && listbox.Visible == true)
{
listbox.Focus();
e.Handled = true;
}
}
private void List_OnKeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Space || e.KeyCode == Keys.Enter)
{
int Selection_Start = this.SelectionStart;
string StrLS = GetLastString(this.Text.Substring(0, Selection_Start));
this.Select(Selection_Start - StrLS.Length, StrLS.Length);
// MessageBox.Show(this.Selection_Start.ToString() + " Last string" + StrLS);
this.SelectedText=((ListBox)sender).SelectedItem.ToString();
listbox.Hide();
this.Focus();
}
}
#endregion
}
public sealed class PopupHelper : IDisposable
{
private readonly Control m_control;
private readonly ToolStripDropDown m_tsdd;
private readonly Panel m_hostPanel; // workarround - some controls don't display correctly if they are hosted directly in ToolStripControlHost
public PopupHelper(Control pControl)
{
m_hostPanel = new Panel();
m_hostPanel.Padding = Padding.Empty;
m_hostPanel.Margin = Padding.Empty;
m_hostPanel.TabStop = false;
m_hostPanel.BorderStyle = BorderStyle.None;
m_hostPanel.BackColor = Color.Transparent;
m_tsdd = new ToolStripDropDown();
m_tsdd.CausesValidation = false;
m_tsdd.Padding = Padding.Empty;
m_tsdd.Margin = Padding.Empty;
m_tsdd.Opacity = 0.9;
m_control = pControl;
m_control.CausesValidation = false;
m_control.Resize += MControlResize;
//m_hostPanel.Controls.Add(m_control);
m_tsdd.Padding = Padding.Empty;
m_tsdd.Margin = Padding.Empty;
m_tsdd.MinimumSize = m_tsdd.MaximumSize = m_tsdd.Size = pControl.Size;
m_tsdd.Items.Add(new ToolStripControlHost(m_control));
}
private void ResizeWindow()
{
m_tsdd.MinimumSize = m_tsdd.MaximumSize = m_tsdd.Size = m_control.Size;
m_hostPanel.MinimumSize = m_hostPanel.MaximumSize = m_hostPanel.Size = m_control.Size;
}
private void MControlResize(object sender, EventArgs e)
{
ResizeWindow();
}
/// <summary>
/// Display the popup and keep the focus
/// </summary>
/// <param name="pParentControl"></param>
public void Show(Control pParentControl)
{
if (pParentControl == null) return;
// position the popup window
var loc = pParentControl.PointToScreen(new Point(0, pParentControl.Height));
pParentControl.Focus();
m_tsdd.Show(loc);
}
public void Hide()
{
m_tsdd.Hide();
}
public void Close()
{
m_tsdd.Close();
}
public void Dispose()
{
m_control.Resize -= MControlResize;
m_tsdd.Dispose();
m_hostPanel.Dispose();
}
}
答案 0 :(得分:0)
首先,我个人认为在另一个控件内部没有任何好处。是的,子控件会自动锁定在其父级边界内,但是您所面临的问题会否定此优势,解决该问题需要与两个控件无关的工作相同。在这两种情况下,您都必须手动执行计算以使子项在其父项中可见。在第二种情况下,父级是应用程序的窗口。
其次,我不建议使用像评论中提到的那样的黑客来显示孩子在其父母的边界之外。正如您所发现的那样,黑客创造的问题比解决的问题多得多。那个黑客究竟是什么意思呢?如果你想在父母之外展示孩子,那么首先不要让它成为儿童控制,你不需要任何黑客攻击。
最好的解决方案是您在任何精心设计的应用程序和Windows本身中找到的解决方案。打开任何应用程序,让我们说记事本,然后右键单击左上角附近。您将看到上下文菜单向右下方向移动。现在右键单击其他三个角附近,您将看到上下文菜单每次都向不同方向拉动,因此它将始终在应用程序内可见。现在,如果您调整应用程序窗口太小并右键单击,上下文菜单将选择最佳方向,但其中一些将在应用程序之外,因为窗口太小。这就是为什么你需要你的清单不是孩子,但这取决于你,而且只是关于这些边缘情况。在这两种情况下,解决方案都是类似的。
您正在显示此行中的列表:
listbox.SetBounds(cp.X + 20, cp.Y + 20, maxWidth, 60);
密钥为cp.X
和cp.Y
。这就决定了列表的显示位置。您需要使此点动态化并响应父级的边界。您将宽度修改为maxWidth
,将高度修改为60
,因此我将在计算中使用这些值。
确保列表不会超出底部:
var y = this.Height < cp.Y + 60 ? this.Height - 60 : cp.Y;
确保列表不会超出右侧:
var x = this.Width < cp.X + maxWidth ? this.Width - maxWidth : cp.X;
现在,您可以在计算点显示列表:
listbox.SetBounds(x, y, maxWidth, 60);
注意:
我没有包含您使用的20个差距。我认为没有差距看起来更好,我没有看到任何有差距的应用程序。如果您更喜欢差距,请将其添加到x
和y
的计算中。不要将其添加到SetBounds()
中,否则会导致计算错误。
上面的计算没有考虑父母大小太小而无法显示孩子的内容。如果你想支持这种边缘情况,你需要让孩子成为一个单独的控件,并在计算中添加一些检查。