web-dev-qa-db-ja.com

C#オートコンプリート

テキストボックスにオートコンプリート機能を追加しようとしていますが、結果はデー​​タベースから取得されます。彼らはの形式で来ます

[001]最後、最初の中間

現在、表示するエントリを取得するには、[001] ...と入力する必要があります。 つまり、問題は、firstnameを最初に入力したとしても完了させたいということです。したがって、エントリが

[001]スミス、ジョンD

johnと入力し始めた場合、このエントリはオートコンプリートの結果に表示されます。

現在、コードは次のようになっています

AutoCompleteStringCollection acsc = new AutoCompleteStringCollection();
txtBox1.AutoCompleteCustomSource = acsc;
txtBox1.AutoCompleteMode = AutoCompleteMode.Suggest; 
txtBox1.AutoCompleteSource = AutoCompleteSource.CustomSource; 

....

if (results.Rows.Count > 0)
    for (int i = 0; i < results.Rows.Count && i < 10; i++) 
    {
        row = results.Rows[i];
        acsc.Add(row["Details"].ToString());
    }
}

resultsはクエリ結果を含むデータセットです

クエリは、likeステートメントを使用した単純な検索クエリです。オートコンプリートを使用せず、結果を配列に投げるだけで正しい結果が返されます。

何かアドバイス?

編集:

結果を返すクエリは次のとおりです

SELECT Name from view_customers where Details LIKE '{0}'

{0}が検索文字列のプレースホルダーです。

43
corymathews

既存のオートコンプリート機能は、プレフィックスによる検索のみをサポートしています。動作をオーバーライドする適切な方法はないようです。

OnTextChangedイベントをオーバーライドすることにより、独自のオートコンプリート機能を実装している人もいます。それはおそらくあなたの最善策です。

たとえば、ListBoxのすぐ下にTextBoxを追加し、デフォルトの可視性をfalseに設定できます。次に、OnTextChangedTextBoxイベントとSelectedIndexChangedListBoxイベントを使用して、アイテムを表示および選択できます。

これは初歩的な例としてはかなりうまくいくようです:

public Form1()
{
    InitializeComponent();


    acsc = new AutoCompleteStringCollection();
    textBox1.AutoCompleteCustomSource = acsc;
    textBox1.AutoCompleteMode = AutoCompleteMode.None;
    textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
}

private void button1_Click(object sender, EventArgs e)
{
    acsc.Add("[001] some kind of item");
    acsc.Add("[002] some other item");
    acsc.Add("[003] an orange");
    acsc.Add("[004] i like pickles");
}

void textBox1_TextChanged(object sender, System.EventArgs e)
{
    listBox1.Items.Clear();
    if (textBox1.Text.Length == 0)
    {
    hideResults();
    return;
    }

    foreach (String s in textBox1.AutoCompleteCustomSource)
    {
    if (s.Contains(textBox1.Text))
    {
        Console.WriteLine("Found text in: " + s);
        listBox1.Items.Add(s);
        listBox1.Visible = true;
    }
    }
}

void listBox1_SelectedIndexChanged(object sender, System.EventArgs e)
{
    textBox1.Text = listBox1.Items[listBox1.SelectedIndex].ToString();
    hideResults();
}

void listBox1_LostFocus(object sender, System.EventArgs e)
{
    hideResults();
}

void hideResults()
{
    listBox1.Visible = false;
}

テキストボックスにテキストを追加したり、追加のキーボードコマンドをキャプチャしたりするなど、あまり手間をかけずにできることがたくさんあります。

47
Steven Richards

ユーザー入力に基づくクエリを使用する場合は、SqlParametersを使用してSQLインジェクション攻撃を回避してください。

SqlCommand sqlCommand = new SqlCommand();
sqlCommand.CommandText = "SELECT Name from view_customers where Details LIKE '%" + @SearchParam + "%'";
sqlCommand.Parameters.AddWithValue("@SearchParam", searchParam);
6
Jim Scott

コンボボックス全体を新しいコントロールに置き換えるのではなく、ComboBoxコントロールクラスを継承する実装を次に示します。テキストボックスに入力すると、独自のドロップダウンが表示されますが、クリックしてドロップリストを表示することは以前と同様に処理されます(つまり、このコードでは処理されません)。そのため、適切なネイティブコントロールと外観が得られます。

あなたがそれを改善したい場合、それを使用し、修正し、答えを編集してください!

class ComboListMatcher : ComboBox, IMessageFilter
{
    private Control ComboParentForm; // Or use type "Form" 
    private ListBox listBoxChild;
    private int IgnoreTextChange;
    private bool MsgFilterActive = false;

    public ComboListMatcher()
    {
        // Set up all the events we need to handle
        TextChanged += ComboListMatcher_TextChanged;
        SelectionChangeCommitted += ComboListMatcher_SelectionChangeCommitted;
        LostFocus += ComboListMatcher_LostFocus;
        MouseDown += ComboListMatcher_MouseDown;
        HandleDestroyed += ComboListMatcher_HandleDestroyed;
    }

    void ComboListMatcher_HandleDestroyed(object sender, EventArgs e)
    {
        if (MsgFilterActive)
            Application.RemoveMessageFilter(this);
    }

    ~ComboListMatcher()
    {
    }

    private void ComboListMatcher_MouseDown(object sender, MouseEventArgs e)
    {
        HideTheList();
    }

    void ComboListMatcher_LostFocus(object sender, EventArgs e)
    {
        if (listBoxChild != null && !listBoxChild.Focused)
            HideTheList();
    }

    void ComboListMatcher_SelectionChangeCommitted(object sender, EventArgs e)
    {
        IgnoreTextChange++;
    }

    void InitListControl()
    {
        if (listBoxChild == null)
        {
            // Find parent - or keep going up until you find the parent form
            ComboParentForm = this.Parent;

            if (ComboParentForm != null)
            {
                // Setup a messaage filter so we can listen to the keyboard
                if (!MsgFilterActive)
                {
                    Application.AddMessageFilter(this);
                    MsgFilterActive = true;
                }

                listBoxChild = listBoxChild = new ListBox();
                listBoxChild.Visible = false;
                listBoxChild.Click += listBox1_Click;
                ComboParentForm.Controls.Add(listBoxChild);
                ComboParentForm.Controls.SetChildIndex(listBoxChild, 0); // Put it at the front
            }
        }
    }


    void ComboListMatcher_TextChanged(object sender, EventArgs e)
    {
        if (IgnoreTextChange > 0)
        {
            IgnoreTextChange = 0;
            return;
        }

        InitListControl();

        if (listBoxChild == null)
            return;

        string SearchText = this.Text;

        listBoxChild.Items.Clear();

        // Don't show the list when nothing has been typed
        if (!string.IsNullOrEmpty(SearchText))
        {
            foreach (string Item in this.Items)
            {
                if (Item != null && Item.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase))
                    listBoxChild.Items.Add(Item);
            }
        }

        if (listBoxChild.Items.Count > 0)
        {
            Point PutItHere = new Point(this.Left, this.Bottom);
            Control TheControlToMove = this;

            PutItHere = this.Parent.PointToScreen(PutItHere);

            TheControlToMove = listBoxChild;
            PutItHere = ComboParentForm.PointToClient(PutItHere);

            TheControlToMove.Show();
            TheControlToMove.Left = PutItHere.X;
            TheControlToMove.Top = PutItHere.Y;
            TheControlToMove.Width = this.Width;

            int TotalItemHeight = listBoxChild.ItemHeight * (listBoxChild.Items.Count + 1);
            TheControlToMove.Height = Math.Min(ComboParentForm.ClientSize.Height - TheControlToMove.Top, TotalItemHeight);
        }
        else
            HideTheList();
    }

    /// <summary>
    /// Copy the selection from the list-box into the combo box
    /// </summary>
    private void CopySelection()
    {
        if (listBoxChild.SelectedItem != null)
        {
            this.SelectedItem = listBoxChild.SelectedItem;
            HideTheList();
            this.SelectAll();
        }
    }

    private void listBox1_Click(object sender, EventArgs e)
    {
        var ThisList = sender as ListBox;

        if (ThisList != null)
        {
            // Copy selection to the combo box
            CopySelection();
        }
    }

    private void HideTheList()
    {
        if (listBoxChild != null)
            listBoxChild.Hide();
    }

    public bool PreFilterMessage(ref Message m)
    {
        if (m.Msg == 0x201) // Mouse click: WM_LBUTTONDOWN
        {
            var Pos = new Point((int)(m.LParam.ToInt32() & 0xFFFF), (int)(m.LParam.ToInt32() >> 16));

            var Ctrl = Control.FromHandle(m.HWnd);
            if (Ctrl != null)
            {
                // Convert the point into our parent control's coordinates ...
                Pos = ComboParentForm.PointToClient(Ctrl.PointToScreen(Pos));

                // ... because we need to hide the list if user clicks on something other than the list-box
                if (ComboParentForm != null)
                {
                    if (listBoxChild != null &&
                        (Pos.X < listBoxChild.Left || Pos.X > listBoxChild.Right || Pos.Y < listBoxChild.Top || Pos.Y > listBoxChild.Bottom))
                    {
                        this.HideTheList();
                    }
                }
            }
        }
        else if (m.Msg == 0x100) // WM_KEYDOWN
        {
            if (listBoxChild != null && listBoxChild.Visible)
            {
                switch (m.WParam.ToInt32())
                {
                    case 0x1B: // Escape key
                        this.HideTheList();
                        return true;

                    case 0x26: // up key
                    case 0x28: // right key
                        // Change selection
                        int NewIx = listBoxChild.SelectedIndex + ((m.WParam.ToInt32() == 0x26) ? -1 : 1);

                        // Keep the index valid!
                        if (NewIx >= 0 && NewIx < listBoxChild.Items.Count)
                            listBoxChild.SelectedIndex = NewIx;
                        return true;

                    case 0x0D: // return (use the currently selected item)
                        CopySelection();
                        return true;
                }
            }
        }

        return false;
    }
}
4
noelicus

これは、あなたが探しているオートコンプリートの振る舞いをあなたに与えるでしょう。

添付の例は完全な作業フォームです。データソースとバインドされた列名が必要です。

using System;
using System.Data;
using System.Windows.Forms;

public partial class frmTestAutocomplete : Form
{

    private DataTable maoCompleteList; //the data table from your data source
    private string msDisplayCol = "name"; //displayed text
    private string msIDcol = "id"; //ID or primary key

    public frmTestAutocomplete(DataTable aoCompleteList, string sDisplayCol, string sIDcol)
    {
        InitializeComponent();

        maoCompleteList = aoCompleteList
        maoCompleteList.CaseSensitive = false; //turn off case sensitivity for searching
        msDisplayCol = sDisplayCol;
        msIDcol = sIDcol;
    }

    private void frmTestAutocomplete_Load(object sender, EventArgs e)
    {

            testCombo.DisplayMember = msDisplayCol;
            testCombo.ValueMember = msIDcol; 
            testCombo.DataSource = maoCompleteList;
            testCombo.SelectedIndexChanged += testCombo_SelectedIndexChanged;
            testCombo.KeyUp += testCombo_KeyUp; 

    }


    private void testCombo_KeyUp(object sender, KeyEventArgs e)
    {
        //use keyUp event, as text changed traps too many other evengts.

        ComboBox oBox = (ComboBox)sender;
        string sBoxText = oBox.Text;

        DataRow[] oFilteredRows = maoCompleteList.Select(MC_DISPLAY_COL + " Like '%" + sBoxText + "%'");

        DataTable oFilteredDT = oFilteredRows.Length > 0
                                ? oFilteredRows.CopyToDataTable()
                                : maoCompleteList;

        //NOW THAT WE HAVE OUR FILTERED LIST, WE NEED TO RE-BIND IT WIHOUT CHANGING THE TEXT IN THE ComboBox.

        //1).UNREGISTER THE SELECTED EVENT BEFORE RE-BINDING, b/c IT TRIGGERS ON BIND.
        testCombo.SelectedIndexChanged -= testCombo_SelectedIndexChanged; //don't select on typing.
        oBox.DataSource = oFilteredDT; //2).rebind to filtered list.
        testCombo.SelectedIndexChanged += testCombo_SelectedIndexChanged;


        //3).show the user the new filtered list.
        oBox.DroppedDown = true; //do this before repainting the text, as it changes the dropdown text.

        //4).binding data source erases text, so now we need to put the user's text back,
        oBox.Text = sBoxText;
        oBox.SelectionStart = sBoxText.Length; //5). need to put the user's cursor back where it was.


    }

    private void testCombo_SelectedIndexChanged(object sender, EventArgs e)
    {

        ComboBox oBox = (ComboBox)sender;

        if (oBox.SelectedValue != null)
        {
            MessageBox.Show(string.Format(@"Item #{0} was selected.", oBox.SelectedValue));
        }
    }
}

//=====================================================================================================
//      code from frmTestAutocomplete.Designer.cs
//=====================================================================================================
partial class frmTestAutocomplete
{
    /// <summary>
    /// Required designer variable.
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// <summary>
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// </summary>
    private void InitializeComponent()
    {
        this.testCombo = new System.Windows.Forms.ComboBox();
        this.SuspendLayout();
        // 
        // testCombo
        // 
        this.testCombo.FormattingEnabled = true;
        this.testCombo.Location = new System.Drawing.Point(27, 51);
        this.testCombo.Name = "testCombo";
        this.testCombo.Size = new System.Drawing.Size(224, 21);
        this.testCombo.TabIndex = 0;
        // 
        // frmTestAutocomplete
        // 
        this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
        this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
        this.ClientSize = new System.Drawing.Size(292, 273);
        this.Controls.Add(this.testCombo);
        this.Name = "frmTestAutocomplete";
        this.Text = "frmTestAutocomplete";
        this.Load += new System.EventHandler(this.frmTestAutocomplete_Load);
        this.ResumeLayout(false);

    }

    #endregion

    private System.Windows.Forms.ComboBox testCombo;
}
1
mike

そのクエリを実行している場合({0}は入力された文字列に置き換えられます)、必要になる場合があります。

SELECT Name from view_customers where Details LIKE '%{0}%'

LIKEには引き続き%文字...そして、はい、ユーザーの入力を信頼するのではなく、パラメーターを使用する必要があります:)

また、Name列を返しているようですが、Details列でクエリを実行しています。だから、誰かが「John Smith」と入力しても、それがDetails列にない場合は、欲しいものを取得できません。

0
Damovisa