组合列表框在应用程序开发中,使用相当频繁,一个好的组合列表框,既能让用户有很好的使用体验,也能提高程序的开发效率。一般使用组合列表框,我们都希望能够实现在提供的选项中进行搜索、通过键盘或鼠标进行选择、支持单选或多选、支持添加选项中不存在的值、多个组合列表框能联动等功能。本控件中的下拉列表使用DataGrid控件进行展示,由于我们在应用开发中,大多数是基于数据库的,所以数据源使用DataView,经测试即使数据源包含万条以上记录也不会有迟滞的感觉。以下是效果图(其中所属单位和单位均为自定义的组合列表框,两个组合列表框能联动):
示例一
示例二
1、控件继承自定义的文本框,所以同时支持在文本框的左右侧显示文本、图片、文本空值时在背景显示提示文本等,边框与其他文本框样式一致。
2、当鼠标移到控件上,或控件或得焦点时,显示右侧的下拉按钮。
3、单击右侧下拉按钮或者在组合列表框获得焦点时,按↑或↓键弹出下拉列表框,并选中当前选定值对应的项。
4、根据指定的搜索字段名称,生成筛选条件格式化字符串,然后在文本框输入文本时,会根据文本值是否是数值,使用不同的的格式化字符串进行搜索。
5、如果允许空值,则按Escape键为控件赋值为Null。
6、如果允许添加列表中不存在的值,则输入值并按F11会检测你输入的值是否符合绑定值的类型,如果符合则添加,否则设为原值。
7、可指定数据源中的哪些列在下拉列表中显示、自动适应表格宽度的列。
8、选定的值为SelectedValue属性。
因为在代码中有详细的注释,所以对于具体的实现请看代码
1、ZbTextBoxSearch.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections;
using System.Windows.Controls.Primitives;
using System.Data;
using Zbsoft.Base;
using System.ComponentModel;
using System.Collections.ObjectModel;
namespace Zbsoft.WpfControls
{
/// <summary>
/// 可以搜索的下拉列表框,选中的值保存在SelectedValue属性,下拉列表框数据源只支持视图
/// </summary>
[TemplatePart(Name = "PART_SearchTextBox", Type = typeof(ZbTextBox))]
[TemplatePart(Name = "PART_popup", Type = typeof(Popup))]
[TemplatePart(Name = "PART_popupDataGrid", Type = typeof(DataGrid))]
public class ZbTextBoxSearch : ZbTextBox
{
/// <summary>
/// 下拉列表框显示的表格
/// </summary>
DataGrid dataGrid;
/// <summary>
/// 下拉列表框
/// </summary>
Popup popup;
/// <summary>
/// 输入文本框
/// </summary>
ZbTextBox inputTextBox;
/// <summary>
/// 下拉按钮
/// </summary>
ToggleButton toggleButton;
/// <summary>
/// 创建时应用指定的样式
/// </summary>
static ZbTextBoxSearch()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ZbTextBoxSearch), new FrameworkPropertyMetadata(typeof(ZbTextBoxSearch)));
}
/// <summary>
/// 是否允许输入列表中没有的信息
/// </summary>
public static readonly DependencyProperty IsCreateNewEnabledProperty =
DependencyProperty.Register("IsCreateNewEnabled", typeof(bool), typeof(ZbTextBoxSearch));
/// <summary>
/// 是否允许输入列表中没有的信息
/// </summary>
public bool IsCreateNewEnabled
{
get { return (bool)GetValue(IsCreateNewEnabledProperty); }
set { SetValue(IsCreateNewEnabledProperty, value); }
}
/// <summary>
/// 最大下拉列表高度
/// </summary>
public static readonly DependencyProperty MaxDropDownHeightProperty =
DependencyProperty.Register("MaxDropDownHeight", typeof(double), typeof(ZbTextBoxSearch));
/// <summary>
/// 最大下拉列表高度
/// </summary>
public double MaxDropDownHeight
{
get { return (double)GetValue(MaxDropDownHeightProperty); }
set { SetValue(MaxDropDownHeightProperty, value); }
}
/// <summary>
/// 最大下拉列表宽度
/// </summary>
public static readonly DependencyProperty MaxDropDownWidthProperty =
DependencyProperty.Register("MaxDropDownWidth", typeof(double), typeof(ZbTextBoxSearch));
/// <summary>
/// 最大下拉列表宽度
/// </summary>
public double MaxDropDownWidth
{
get { return (double)GetValue(MaxDropDownWidthProperty); }
set { SetValue(MaxDropDownWidthProperty, value); }
}
/// <summary>
/// 下拉列表最小行高度
/// </summary>
public static readonly DependencyProperty MinDropDownRowHeightProperty =
DependencyProperty.Register("MinDropDownRowHeight", typeof(double), typeof(ZbTextBoxSearch));
/// <summary>
/// 下拉列表最小行高度
/// </summary>
public double MinDropDownRowHeight
{
get { return (double)GetValue(MinDropDownRowHeightProperty); }
set { SetValue(MinDropDownRowHeightProperty, value); }
}
/// <summary>
/// 下拉列表是否展开
/// </summary>
public static readonly DependencyProperty IsDropDownOpenProperty =
DependencyProperty.Register("IsDropDownOpen", typeof(bool), typeof(ZbTextBoxSearch));
/// <summary>
/// 下拉列表是否展开
/// </summary>
public bool IsDropDownOpen
{
get { return (bool)GetValue(IsDropDownOpenProperty); }
set { SetValue(IsDropDownOpenProperty, value); }
}
/// <summary>
/// 下拉列表项选择模式,单选或多选
/// </summary>
public static readonly DependencyProperty SelectionModeProperty =
DependencyProperty.Register("SelectionMode", typeof(DataGridSelectionMode), typeof(ZbTextBoxSearch));
/// <summary>
/// 下拉列表项选择模式,单选或多选
/// </summary>
public DataGridSelectionMode SelectionMode
{
get { return (DataGridSelectionMode)GetValue(SelectionModeProperty); }
set { SetValue(SelectionModeProperty, value); }
}
/// <summary>
/// 下拉列表表头显示选项
/// </summary>
public static readonly DependencyProperty HeadersVisibilityProperty =
DependencyProperty.Register("HeadersVisibility", typeof(DataGridHeadersVisibility), typeof(ZbTextBoxSearch));
/// <summary>
/// 下拉列表表头显示选项
/// </summary>
public DataGridHeadersVisibility HeadersVisibility
{
get { return (DataGridHeadersVisibility)GetValue(HeadersVisibilityProperty); }
set { SetValue(HeadersVisibilityProperty, value); }
}
/// <summary>
/// 下拉列表是否自动填充,默认为自动填充
/// </summary>
public static readonly DependencyProperty AutoGenerateColumnsProperty =
DependencyProperty.Register("AutoGenerateColumns", typeof(bool), typeof(ZbTextBoxSearch));
/// <summary>
/// 下拉列表是否自动填充,默认为自动填充
/// </summary>
public bool AutoGenerateColumns
{
get { return (bool)GetValue(AutoGenerateColumnsProperty); }
set { SetValue(AutoGenerateColumnsProperty, value); }
}
/// <summary>
/// 下拉列表的数据源
/// </summary>
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(DataView), typeof(ZbTextBoxSearch), new PropertyMetadata(OnItemSourceChanged));
/// <summary>
/// 下拉列表的数据源
/// </summary>
public DataView ItemsSource
{
get { return (DataView)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue",
typeof(object), typeof(ZbTextBoxSearch), new PropertyMetadata(OnSelectedValueChanged));
/// <summary>
/// 选定的值,与此属性绑定
/// </summary>
public object SelectedValue
{
get { return (object)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static DependencyProperty DisplaySeparatorCharProperty = DependencyProperty.Register("DisplaySeparatorChar",
typeof(string), typeof(ZbTextBoxSearch), new PropertyMetadata(OnSelectedValueChanged));
/// <summary>
/// 选定显示值之间的间隔字符
/// </summary>
public string DisplaySeparatorChar
{
get { return (string)GetValue(DisplaySeparatorCharProperty); }
set { SetValue(DisplaySeparatorCharProperty, value); }
}
public static DependencyProperty ValueSeparatorCharProperty = DependencyProperty.Register("ValueSeparatorChar",
typeof(char), typeof(ZbTextBoxSearch), new PropertyMetadata(OnSelectedValueChanged));
/// <summary>
/// 选定实际值之间的间隔字符
/// </summary>
public char ValueSeparatorChar
{
get { return (char)GetValue(ValueSeparatorCharProperty); }
set { SetValue(ValueSeparatorCharProperty, value); }
}
public static DependencyProperty DropDownDisplayFieldsProperty = DependencyProperty.Register("DropDownDisplayFields",
typeof(string), typeof(ZbTextBoxSearch));
/// <summary>
/// 下拉列表框中显示的字段名
/// </summary>
public string DropDownDisplayFields
{
get { return (string)GetValue(DropDownDisplayFieldsProperty); }
set { SetValue(DropDownDisplayFieldsProperty, value); }
}
public static DependencyProperty ValueMemberProperty = DependencyProperty.Register("ValueMember",
typeof(string), typeof(ZbTextBoxSearch), new PropertyMetadata(OnValueMemberChanged));
/// <summary>
/// 值对应字段名
/// </summary>
public string ValueMember
{
get{return (string)GetValue(ValueMemberProperty);}
set { SetValue(ValueMemberProperty, value); }
}
public static DependencyProperty DisplayMemberProperty = DependencyProperty.Register("DisplayMember",
typeof(string), typeof(ZbTextBoxSearch), new PropertyMetadata(OnSelectedValueChanged));
/// <summary>
/// 选中值对应的显示字段名
/// </summary>
public string DisplayMember
{
get { return (string)GetValue(DisplayMemberProperty); }
set { SetValue(DisplayMemberProperty, value); }
}
/// <summary>
/// 不包含数值字段的格式化字符串
/// </summary>
private string searchFormatString;
/// <summary>
/// 包含数值字段的格式化字符串
/// </summary>
private string searchFormatStringNumber;
public static DependencyProperty SearchFieldsProperty = DependencyProperty.Register("SearchFields",
typeof(string), typeof(ZbTextBoxSearch), new PropertyMetadata(OnSearchFieldsChanged));
/// <summary>
/// 可搜索的字段名,多个字段间用逗号间隔,只对字符型和数值型字段进行搜索,如果包含其他类型字段,则出错时仅在字符型字段中进行搜索
/// </summary>
public string SearchFields
{
get { return (string)GetValue(SearchFieldsProperty); }
set { SetValue(SearchFieldsProperty, value); }
}
public static DependencyProperty FiltAddProperty = DependencyProperty.Register("FiltAdd",
typeof(string), typeof(ZbTextBoxSearch), new PropertyMetadata(OnSearchFieldsChanged));
/// <summary>
/// 附加的搜索条件
/// </summary>
public string FiltAdd
{
get { return (string)GetValue(FiltAddProperty); }
set { SetValue(FiltAddProperty, value); }
}
public static DependencyProperty AutoFiltAddValueProperty = DependencyProperty.Register("AutoFiltAddValue",
typeof(object), typeof(ZbTextBoxSearch), new PropertyMetadata(OnSearchFieldsChanged));
/// <summary>
/// 自动附加的搜索条件值
/// </summary>
public object AutoFiltAddValue
{
get { return (object)GetValue(AutoFiltAddValueProperty); }
set { SetValue(AutoFiltAddValueProperty, value); }
}
public static DependencyProperty AutoFiltAddFormatStringProperty = DependencyProperty.Register("AutoFiltAddFormatString",
typeof(string), typeof(ZbTextBoxSearch), new PropertyMetadata(OnSearchFieldsChanged));
/// <summary>
/// 自动附加的搜索条件格式化字符串
/// </summary>
public string AutoFiltAddFormatString
{
get { return (string)GetValue(AutoFiltAddFormatStringProperty); }
set { SetValue(AutoFiltAddFormatStringProperty, value); }
}
public static DependencyProperty DisplayColumnsProperty = DependencyProperty.Register("DisplayColumns",
typeof(string), typeof(ZbTextBoxSearch), new PropertyMetadata(OnDisplayColumnsChanged));
/// <summary>
/// 获取或设置下拉列表框显示的列名称,多个列名称间以逗号间隔
/// </summary>
public string DisplayColumns
{
get { return (string)GetValue(DisplayColumnsProperty); }
set { SetValue(DisplayColumnsProperty, value); }
}
public static DependencyProperty DisplayColumnsHeadTextProperty = DependencyProperty.Register("DisplayColumnsHeadText",
typeof(string), typeof(ZbTextBoxSearch), new PropertyMetadata(OnDisplayColumnsChanged));
/// <summary>
/// 获取或设置下拉列表框显示列的标题,格式为:列名称1,列标题1;列名称2,列标题2
/// </summary>
public string DisplayColumnsHeadText
{
get { return (string)GetValue(DisplayColumnsHeadTextProperty); }
set { SetValue(DisplayColumnsHeadTextProperty, value); }
}
public static DependencyProperty AutoWidthColumnNameProperty = DependencyProperty.Register("AutoWidthColumnName",
typeof(string), typeof(ZbTextBoxSearch));
/// <summary>
/// 自动填充满表格的列名称
/// </summary>
public string AutoWidthColumnName
{
get { return (string)GetValue(AutoWidthColumnNameProperty); }
set { SetValue(AutoWidthColumnNameProperty, value); }
}
/// <summary>
/// 获取搜索格式化字符串
/// </summary>
private void GetSearchFormatString()
{
if (this.ItemsSource == null || this.ItemsSource.Table.Columns.Count == 0)
{
this.searchFormatString = "";
return;
}
string[] searchFieldArray;
if (this.SearchFields == null)
searchFieldArray = new string[] { this.ValueMember, this.DisplayMember };
else
searchFieldArray = this.SearchFields.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
this.searchFormatString = ""; //搜索值为文本的格式化字符串
this.searchFormatStringNumber = ""; //搜索值为数值的格式化字符串
string link = "";
string linkNumber = "";
foreach (var v in searchFieldArray)
{
if (string.IsNullOrEmpty(v))
continue;
if (!this.ItemsSource.Table.Columns.Contains(v.Trim()))
continue;
if (this.ItemsSource.Table.Columns[v.Trim()].DataType == typeof(string) ||
this.ItemsSource.Table.Columns[v.Trim()].DataType == typeof(char))
{
//文本用模糊搜索
this.searchFormatString += string.Format(" {0} {1} like '%{2}%' ", link, v, "{0}");
this.searchFormatStringNumber += string.Format(" {0} {1} like '%{2}%' ", linkNumber, v, "{0}");
link = " or ";
linkNumber = " or ";
}
else
{
//数值用精确搜索
this.searchFormatStringNumber += string.Format(" {0} {1} = {2} ", link, v, "{0}");
linkNumber = " or ";
}
}
string addSearchString = this.GetAddFiltString();
//如果有附加搜索条件或者有自动附加搜索条件,则添加
if (!string.IsNullOrEmpty(addSearchString))
{
if (string.IsNullOrEmpty(this.searchFormatString))
this.searchFormatString = addSearchString;
else
this.searchFormatString = "(" + this.searchFormatString + ") and (" + addSearchString + ")";
if (string.IsNullOrEmpty(this.searchFormatStringNumber))
this.searchFormatStringNumber = addSearchString;
else
this.searchFormatStringNumber = "(" + this.searchFormatStringNumber + ") and (" + addSearchString + ")";
}
}
/// <summary>
/// 获取附加筛选字符串
/// </summary>
/// <returns></returns>
private string GetAddFiltString()
{
if (!string.IsNullOrEmpty(this.FiltAdd) ||
(!string.IsNullOrEmpty(this.AutoFiltAddFormatString) &&
!string.IsNullOrEmpty(this.AutoFiltAddValue.ToStringNull())))
{
string addSearchString = "";
if (!string.IsNullOrEmpty(this.FiltAdd))
addSearchString = this.FiltAdd;
if (!string.IsNullOrEmpty(this.AutoFiltAddFormatString) &&
!string.IsNullOrEmpty(this.AutoFiltAddValue.ToStringNull()))
if (string.IsNullOrEmpty(addSearchString))
addSearchString = string.Format(this.AutoFiltAddFormatString, this.AutoFiltAddValue);
else
addSearchString += " and " + string.Format(this.AutoFiltAddFormatString, this.AutoFiltAddValue);
return addSearchString;
}
return null;
}
/// <summary>
/// 当选定的值改变时,获取对应显示的值,并在文本框中予以显示
/// </summary>
private void GetDisplayValue()
{
if (this.inputTextBox == null) return;
if (this.ItemsSource == null || string.IsNullOrEmpty(this.DisplayMember)
|| string.IsNullOrEmpty(this.ValueMember) || this.SelectedValue == null)
{
this.inputTextBox.Text = null;
return;
}
object[] vs;
//如果允许多选,则对选定的值按隔离字符进行分割
if (this.SelectionMode == DataGridSelectionMode.Extended)
{
vs = this.SelectedValue.ToStringNull().Split(new char[] { this.ValueSeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
}
else
{
vs = new object[] { this.SelectedValue };
}
string tmpText = "";
string tmpSeparatorChar = "";
//循环获取各个选定值对应的显示文本,并按指定的显示隔离符进行连接
foreach (var v in vs)
{
DataRow[] rs = this.ItemsSource.Table.Select(string.Format("{0} = '{1}'", this.ValueMember, v));
if (rs.Length > 0)
tmpText += tmpSeparatorChar + rs[0][this.DisplayMember].ToStringNull();
else
tmpText += tmpSeparatorChar + v.ToStringNull();
tmpSeparatorChar = this.DisplaySeparatorChar.ToStringNull();
}
this.inputTextBox.Text = tmpText;
}
/// <summary>
/// 获得焦点时,将焦点设为文本框
/// </summary>
/// <param name="e"></param>
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if (!this.IsDropDownOpen)
this.inputTextBox.Focus();
}
/// <summary>
/// 模板应用时,对下拉列表进行初始化,这个是实现各个功能的关键
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//获取搜索文本框
this.inputTextBox = Zbsoft.WpfControls.ZbExternt.GetChildObject<ZbTextBox>(this, "PART_SearchTextBox");
//获取弹出窗口
this.popup = Zbsoft.WpfControls.ZbExternt.GetChildObject<Popup>(this, "PART_popup");
if (this.popup !=null && this.MaxDropDownWidth > 2)
this.popup.MaxWidth = this.MaxDropDownWidth;
//获取下拉列表框中的表格
this.dataGrid = Zbsoft.WpfControls.ZbExternt.GetChildObject<DataGrid>(this.popup.Child, "PART_popupDataGrid");
if (this.inputTextBox != null)
{
//获取搜索文本框右侧的下拉按钮
this.toggleButton = this.inputTextBox.RightContent as ToggleButton;
this.inputTextBox.PreviewKeyDown += new KeyEventHandler(inputTextBox_PreviewKeyDown);
this.inputTextBox.PreviewKeyUp += new KeyEventHandler(inputTextBox_PreviewKeyUp);
this.inputTextBox.PreviewTextInput += new TextCompositionEventHandler(inputTextBox_PreviewTextInput);
this.toggleButton.Click += new RoutedEventHandler(toggleButton_Click);
}
if (this.dataGrid != null)
{
this.dataGrid.PreviewKeyDown += new KeyEventHandler(dataGrid_PreviewKeyDown);
this.dataGrid.MouseDoubleClick += new MouseButtonEventHandler(dataGrid_MouseDoubleClick);
this.dataGrid.AutoGeneratedColumns += new EventHandler(dataGrid_AutoGeneratedColumns);
this.dataGrid.Loaded += new RoutedEventHandler(dataGrid_Loaded);
}
}
/// <summary>
/// 下拉列表表格加载完成后,设置自动填充满表格的列宽度,
/// 这个是因为DataGrid在Popup中即使某列的宽度设为*,仍然不能自动填满空白,而不在Popup中的DataGrid却能自动填满,
/// 这个不知是什么缘故,会不会是微软坑爹
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void dataGrid_Loaded(object sender, RoutedEventArgs e)
{
this.SetAutoColoumnWidth();
}
/// <summary>
/// 自动列生成所有列时,设置显示列和标题
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void dataGrid_AutoGeneratedColumns(object sender, EventArgs e)
{
this.SetPopuDataGridColumn();
}
/// <summary>
/// 点击弹出下拉列表框,选中当前值
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void toggleButton_Click(object sender, RoutedEventArgs e)
{
if (this.dataGrid == null || this.ItemsSource == null) return;
this.dataGrid.SelectedIndex = -1;
if (this.SelectedValue == null)
{
return;
}
try
{
this.ItemsSource.RowFilter = this.GetAddFiltString();
}
catch { }
object[] vs;
//如果允许多选
if (this.SelectionMode == DataGridSelectionMode.Extended)
{
vs = this.SelectedValue.ToStringNull().Split(new char[] { this.ValueSeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
}
else
{
//vs = new object[] { this.SelectedValue };
vs = new object[] { ZbExternt.TypeToType(this.SelectedValue, this.ItemsSource.Table.Columns[this.ValueMember].DataType) };
}
int rs = 0;
//循环将选中的值对应的表格行设为选中状态
for (int i = 0; i < this.dataGrid.Items.Count; i++)
{
if (this.SelectionMode == DataGridSelectionMode.Extended)
{
if (vs.Contains((this.dataGrid.Items[i] as DataRowView)[this.ValueMember].ToStringNull()))
{
this.dataGrid.ScrollIntoView(this.dataGrid.Items[i]);
//因为WPF显示表格是采用虚拟技术,所以只有通过上一条语句将表格滚动到对应的行后,才能确保下面一条语句能够真正获取到表格行,否则可能为空
DataGridRow row = (DataGridRow)this.dataGrid.ItemContainerGenerator.ContainerFromIndex(i);
if (row == null)
continue;
row.IsSelected = true;
rs++;
//如果所有选中值都已处理,则退出
if (rs == vs.Length)
break;
continue;
}
}
else
{
if (vs.Contains((this.dataGrid.Items[i] as DataRowView)[this.ValueMember]))
{
this.dataGrid.ScrollIntoView(this.dataGrid.Items[i]);
//因为WPF显示表格是采用虚拟技术,所以只有通过上一条语句将表格滚动到对应的行后,才能确保下面一条语句能够真正获取到表格行,否则可能为空
DataGridRow row = (DataGridRow)this.dataGrid.ItemContainerGenerator.ContainerFromIndex(i);
if (row == null)
break;
row.IsSelected = true;
break;
}
}
}
}
/// <summary>
/// 鼠标双击时选定,并移动到下一个焦点
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void dataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
this.IsDropDownOpen = false;
this.inputTextBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
/// <summary>
/// 弹出下拉列表框且下拉列表框获取焦点时,按回车键选中,并移动到下一个焦点
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void dataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
this.inputTextBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
e.Handled = true;
}
}
/ <summary>
/ 在表格中按回车键选择
/ </summary>
/ <param name="e"></param>
//protected override void OnPreviewKeyDown(KeyEventArgs e)
//{
// this.isAddNewValue = false;
// //如果按Esc键,并且允许为空值,则清空选择的值
// if (e.Key == Key.Escape)
// {
// if (this.EnableNullValue)
// {
// if (this.dataGrid != null)
// this.dataGrid.SelectedIndex = -1;
// this.SelectedValue = null;
// }
// e.Handled = true;
// this.IsDropDownOpen = false;
// return;
// }
// base.OnPreviewKeyDown(e);
//}
private bool isTextInput = false;
/// <summary>
/// 输入文本时对下拉列表进行筛选
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void inputTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
this.isTextInput = true;
}
/// <summary>
/// 是否是添加一个新值
/// </summary>
private bool isAddNewValue;
/// <summary>
/// 按键时对下拉列表进行筛选
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void inputTextBox_PreviewKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete || e.Key == Key.Back || e.Key == Key.Space || this.isTextInput)
popupAndFilter();
}
/// <summary>
/// 输入框按键处理,如果是向上或向下的方向键则移动下拉列表框中的选定项
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void inputTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
this.isTextInput = false;
this.isAddNewValue = false;
//如果按Esc键,并且允许为空值,则清空选择的值
if (e.Key == Key.Escape)
{
if (this.EnableNullValue)
{
if (this.dataGrid != null)
this.dataGrid.SelectedIndex = -1;
this.SelectedValue = null;
}
e.Handled = true;
this.IsDropDownOpen = false;
return;
}
//如果是按了F11键,则是输入列表中不存在的值
if (e.Key == Key.F11)
{
//设置为未选中任意行且为输入新增值状态,当失去焦点时会自动判断是否允许新增及新增值是否能转换为需要的值类型
this.isAddNewValue = true;
if (this.dataGrid != null)
this.dataGrid.SelectedIndex = -1;
this.IsDropDownOpen = false;
e.Handled = true;
this.inputTextBox.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
return;
}
//如果是向上或向下的方向键则移动下拉列表框中的选定项
if (e.Key == Key.Down || e.Key == Key.Up)
{
if (this.popup == null) return;
if (!this.popup.IsOpen)
{
this.IsDropDownOpen = true;
this.toggleButton_Click(null, null);
return;
}
if (this.dataGrid == null || this.dataGrid.Items.Count == 0) return;
if (e.Key == Key.Down)
{
this.dataGrid.SelectedIndex++;
}
else
{
if (this.dataGrid.SelectedIndex > 0)
this.dataGrid.SelectedIndex--;
}
if (this.dataGrid.SelectedIndex >= 0)
this.dataGrid.ScrollIntoView(this.dataGrid.Items[this.dataGrid.SelectedIndex]);
}
}
/// <summary>
/// 弹出窗口并对下拉列表中的表格显示行以输入框中的值为条件进行过滤
/// </summary>
private void popupAndFilter()
{
if (this.dataGrid == null || this.ItemsSource == null) return;
if (!this.IsDropDownOpen)
this.IsDropDownOpen = true;
decimal o;
if (string.IsNullOrEmpty(this.inputTextBox.Text))
try
{
this.ItemsSource.RowFilter = this.GetAddFiltString();
}
catch { }
else
{
if (decimal.TryParse(this.inputTextBox.Text, out o))
{
if (string.IsNullOrEmpty(this.searchFormatStringNumber))
{
this.ItemsSource.RowFilter = "";
return;
}
try
{
this.ItemsSource.RowFilter = string.Format(this.searchFormatStringNumber, this.inputTextBox.Text);
}
catch
{
if (string.IsNullOrEmpty(this.searchFormatString))
{
this.ItemsSource.RowFilter = "";
return;
}
try
{
this.ItemsSource.RowFilter = string.Format(this.searchFormatString, this.inputTextBox.Text.EscapeLikeValue());
}
catch { }
}
}
else
{
if (string.IsNullOrEmpty(this.searchFormatString))
{
this.ItemsSource.RowFilter = "";
return;
}
try
{
this.ItemsSource.RowFilter = string.Format(this.searchFormatString, this.inputTextBox.Text.EscapeLikeValue());
}
catch { }
}
}
}
/// <summary>
/// 当本控件以及本控件中的子元素均无焦点时关闭下拉列表框,并设置新值和新的显示值
/// </summary>
/// <param name="e"></param>
protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e)
{
base.OnIsKeyboardFocusWithinChanged(e);
//如果失去焦点
if (!(bool)e.NewValue && !this.IsReadOnly)
{
object v = this.GetSelectedValue();
this.isAddNewValue = false;
//如果原值与现值不同,则更新选定值属性值
if (v.ToStringNull() != this.SelectedValue.ToStringNull())
{
if (string.IsNullOrEmpty(v.ToStringNull()) && !this.EnableNullValue)
{
this.SelectedValue = this.SelectedValue;
}
else
this.SelectedValue = v;
}
else
GetDisplayValue();//否则以原值更新显示值
}
if (this.popup == null) return;
if (this.IsKeyboardFocusWithin)
return;
this.popup.IsOpen = false;
}
/// <summary>
/// 获取选定的值
/// </summary>
/// <returns></returns>
private object GetSelectedValue()
{
if (string.IsNullOrEmpty(this.ValueMember))
return null;
if (this.ItemsSource == null)
return null;
if (!this.ItemsSource.Table.Columns.Contains(this.ValueMember))
return null;
object v;
//如果没有选中任意行
if (this.dataGrid == null || this.dataGrid.SelectedItems.Count == 0)
{
//如果是新增列表框没有的值,且允许新增
if (this.isAddNewValue && this.IsCreateNewEnabled)
{
//将文本框中的值转换为需要的值类型,如果不能完成转换,则返回空值。
v = Zbsoft.WpfControls.ZbExternt.TypeToType(this.inputTextBox.Text, this.ItemsSource.Table.Columns[this.ValueMember].DataType);
return v;
}
//否则返回原值
return this.SelectedValue;
}
//如果选中多行,返回值为选中值转换为字符串后按值分隔符进行连接返回的字符串
if (this.dataGrid.SelectedItems.Count > 1)
{
StringBuilder sv = new StringBuilder();
foreach (var item in this.dataGrid.SelectedItems)
{
sv.Append((item as DataRowView)[this.ValueMember]);
sv.Append(this.ValueSeparatorChar);
}
v = sv.ToString(0, sv.Length - 1);
}
else
{
//否则返回选中值的值,值类型不变
v = (this.dataGrid.SelectedItems[0] as DataRowView)[this.ValueMember];
}
return v;
}
/// <summary>
/// 当依赖项属性“选定的值”属性值改变时,更新显示值
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnSelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ZbTextBoxSearch)d).GetDisplayValue();
}
/// <summary>
/// 当列表框数据源属性值改变时,完成数据绑定、更新显示值
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnItemSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ZbTextBoxSearch)d).SetItemSource((DataView)e.NewValue);
}
/// <summary>
/// 设置下拉列表表格的数据源,并更新显示值、搜索格式化字符串、设置表格显示格式
/// </summary>
/// <param name="view"></param>
private void SetItemSource(DataView view)
{
if (this.dataGrid == null) return;
this.dataGrid.ItemsSource = view;
this.GetDisplayValue();
this.GetSearchFormatString();
this.SetPopuDataGridColumn();
}
/// <summary>
/// 当值字段名称属性值改变时,更新显示值和搜索格式化字符串
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnValueMemberChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ZbTextBoxSearch)d).GetDisplayValue();
((ZbTextBoxSearch)d).GetSearchFormatString();
}
/// <summary>
/// 搜索字段属性值改变时,重新生成搜索格式化字符串
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnSearchFieldsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ZbTextBoxSearch)d).GetSearchFormatString();
}
/// <summary>
/// 下拉列表框显示列、和下拉列表框显示列的标题属性更改时刷新列表显示
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnDisplayColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ZbTextBoxSearch)d).SetPopuDataGridColumn();
}
/// <summary>
/// 设置下拉列表框显示列的表头文本,以及隐藏不显示的列
/// </summary>
private void SetPopuDataGridColumn()
{
if (!this.AutoGenerateColumns) return;
if (this.dataGrid == null || this.ItemsSource == null) return;
string[] cols;
string[] heads;
Dictionary<string, string> headTexts = new Dictionary<string,string>();
//以显示表格列字符串(格式为:列名1,列名2)生成显示列名称数组
if (!string.IsNullOrEmpty(this.DisplayColumns))
cols = this.DisplayColumns.ToUpper().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
else
cols = null;
//以显示表格标题字符串(格式为:列名1,列标题1;列名2,列标题2)生成表格标题文本字典,键名为字段名(大写),键值为标题文本
if (!string.IsNullOrEmpty(this.DisplayColumnsHeadText))
{
heads = this.DisplayColumnsHeadText.Split(new char[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
if (heads.Length % 2 == 0)
{
for(int i = 0; i < heads.Length; i = i + 2)
{
headTexts.Add(heads[i].ToUpper(), heads[i + 1]);
}
}
}
if (cols != null || headTexts.Count > 0)
{
for (int i = 0; i < this.dataGrid.Columns.Count; i++)
{
//自动生成的列为DataGridTextColumn类型
DataGridTextColumn col = this.dataGrid.Columns[i] as DataGridTextColumn;
if (col == null) continue;
//根据绑定的Path属性来匹配字段名,如果匹配则设置是否显示以及标题文本
Binding b = col.Binding as Binding;
if (b != null)
{
if (cols != null && !cols.Contains(b.Path.Path.ToUpper()))
col.Visibility = System.Windows.Visibility.Hidden;
if (headTexts.ContainsKey(b.Path.Path.ToUpper()))
col.Header = headTexts[b.Path.Path.ToUpper()];
}
}
//如果仅显示一列,则将该列设为自动适应表格宽度的列以填充满表格
if (cols != null && cols.Length == 1)
this.AutoWidthColumnName = cols[0];
}
}
/// <summary>
/// 设置自动填充满表格的列宽度,这个主要是DataGrid在Popup中显示时不能很好的自动适应表格宽度而做的,在表格加载完成时调用
/// </summary>
private void SetAutoColoumnWidth()
{
if (!this.AutoGenerateColumns) return;
if (string.IsNullOrEmpty(this.AutoWidthColumnName)) return;
double dataGridWidth = this.dataGrid.ActualWidth;
int index = -1;//自动适应表格宽度的列所在的列号
double otherColumnsWidth = 0;
for (int i = 0; i < this.dataGrid.Columns.Count; i++)
{
DataGridTextColumn col = this.dataGrid.Columns[i] as DataGridTextColumn;
if (col == null) continue;
Binding b = col.Binding as Binding;
if (b != null && b.Path.Path.ToUpper() == this.AutoWidthColumnName)
index = i;
else
otherColumnsWidth += col.Visibility != System.Windows.Visibility.Visible ? 0 : col.Width.DesiredValue;
}
if (index == -1) return;
if (dataGridWidth - otherColumnsWidth - 13 < this.dataGrid.Columns[index].Width.DesiredValue) return;
this.dataGrid.Columns[index].Width = new DataGridLength(dataGridWidth - otherColumnsWidth - 12, DataGridLengthUnitType.Pixel);
}
}
}
2、ZbTextBoxSearch.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Zbsoft.WpfControls">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Zbsoft.WpfControls;component/Resources/ZbShared.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
<Style x:Key="SelectedRow" TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="Blue"></Setter>
</Style>
<Style TargetType="{x:Type local:ZbTextBoxSearch}">
<Setter Property="SnapsToDevicePixels"
Value="True" />
<Setter Property="CornerRadius"
Value="{StaticResource TextBoxBorderCornerRadius}"></Setter>
<Setter Property="BorderThickness"
Value="{StaticResource TextBoxBorderThickness}"></Setter>
<Setter Property="Padding"
Value="{StaticResource TextBoxPadding}"></Setter>
<Setter Property="LeftPromptWidth"
Value="0"></Setter>
<Setter Property="LeftPromptMargin"
Value="0,0,2,0"></Setter>
<Setter Property="RightPromptWidth"
Value="0"></Setter>
<Setter Property="BorderThickness"
Value="{StaticResource TextBoxBorderThickness}"></Setter>
<Setter Property="Padding"
Value="{StaticResource TextBoxPadding}"></Setter>
<Setter Property="BorderBrush"
Value="{StaticResource TextBoxBorderDefaultBrush}"></Setter>
<Setter Property="Foreground"
Value="{StaticResource TextBoxForeground}"></Setter>
<Setter Property="Background"
Value="{StaticResource TextBoxBackground}"></Setter>
<Setter Property="PromptForeground"
Value="{StaticResource TextBoxPromptForeground}"></Setter>
<Setter Property="KeyboardNavigation.TabNavigation"
Value="None" />
<Setter Property="FocusVisualStyle"
Value="{x:Null}" />
<Setter Property="RightContentAlwaysVisible" Value="Collapsed"></Setter>
<Setter Property="MaxDropDownHeight" Value="100"></Setter>
<Setter Property="MinDropDownRowHeight" Value="25"></Setter>
<Setter Property="SelectionMode" Value="Single"></Setter>
<Setter Property="HeadersVisibility" Value="Column"></Setter>
<Setter Property="AutoGenerateColumns" Value="True"></Setter>
<Setter Property="DisplaySeparatorChar" Value=","></Setter>
<Setter Property="ValueSeparatorChar" Value=","></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ZbTextBoxSearch}">
<Grid HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{TemplateBinding LeftPromptWidth}"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="{TemplateBinding RightPromptWidth}"></ColumnDefinition>
</Grid.ColumnDefinitions>
<!--左侧文本-->
<TextBlock x:Name="PART_LeftPromptTextBlock"
Foreground="{TemplateBinding PromptForeground}"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Text="{TemplateBinding LeftPromptString}"
Margin="{TemplateBinding LeftPromptMargin}"></TextBlock>
<!--右侧文本-->
<TextBlock x:Name="PART_RightPromptTextBlock"
Foreground="{TemplateBinding PromptForeground}"
Grid.Column="2"
VerticalAlignment="Center"
Text="{TemplateBinding RightPromptString}"
Margin="{TemplateBinding RightPromptMargin}"></TextBlock>
<local:ZbTextBox x:Name="PART_SearchTextBox"
Grid.Column="1"
LeftContent="{TemplateBinding LeftContent}"
BackPromptString="{TemplateBinding BackPromptString}"
>
<local:ZbTextBox.RightContent>
<ToggleButton Name="toggleButton" IsTabStop="False" VerticalAlignment="Stretch"
Focusable="False"
Template="{StaticResource ToggleButtonTemplate}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsDropDownOpen, Mode=TwoWay}"
>
<!--<StackPanel Name="PART_labelContentPanel" IsHitTestVisible="False" Margin="4,0,5,0" Orientation="Horizontal"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" />-->
</ToggleButton>
</local:ZbTextBox.RightContent>
</local:ZbTextBox>
<Popup Name="PART_popup"
StaysOpen="False"
AllowsTransparency="True"
Placement="Bottom" PlacementTarget="{Binding ElementName=PART_SearchTextBox}"
IsOpen="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsDropDownOpen}"
MinWidth="{Binding Path=ActualWidth, ElementName=PART_SearchTextBox}"
MinHeight="80"
PopupAnimation="Slide">
<Grid>
<Border BorderBrush="{StaticResource PopupBorderDefaultBrush}"
Name="PART_popuppanel"
CornerRadius="{StaticResource PopupBorderCornerRadius}"
BorderThickness="{StaticResource PopupBorderThickness}"
Background="{StaticResource PopupBackground}"
Padding="{StaticResource PopupPadding}"
Margin="0,2,3,3">
<DataGrid Name="PART_popupDataGrid"
IsReadOnly="True"
SelectionUnit="FullRow"
SelectionMode="{TemplateBinding SelectionMode}"
AutoGenerateColumns="{TemplateBinding AutoGenerateColumns}"
HeadersVisibility="{TemplateBinding HeadersVisibility}"
MinRowHeight="{TemplateBinding MinDropDownRowHeight}"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
AlternationCount="2"
>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<Trigger Property="AlternationIndex" Value="0" >
<Setter Property="Background" Value="#FFFFFFFF" />
</Trigger>
<Trigger Property="AlternationIndex" Value="1" >
<Setter Property="Background" Value="#f2f2f2ff" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="#ffffff"/>
<Setter Property="Background" Value="#0000ff" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="#ffffff"/>
<Setter Property="Background" Value="#00000000" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
</DataGrid>
<Border.Effect>
<DropShadowEffect ShadowDepth="2"/>
</Border.Effect>
</Border>
</Grid>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
3、测试窗口
UserEdit.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Zbsoft.WpfControls;
using System.Data;
namespace Zbsoft.OfficeClient
{
/// <summary>
/// UserEdit.xaml 的交互逻辑
/// </summary>
public partial class UserEdit : ZbWindow
{
public UserEdit()
{
InitializeComponent();
}
private void ZbWindow_Loaded(object sender, RoutedEventArgs e)
{
DataTable table = new DataTable();
DataColumn c1 = new DataColumn("单位名称", typeof(string));
DataColumn c2 = new DataColumn("单位代码", typeof(int));
DataColumn c3 = new DataColumn("所属单位代码", typeof(int));
table.Columns.Add(c1);
table.Columns.Add(c2);
table.Columns.Add(c3);
for (int i = 0; i <= 200; i++)
{
DataRow r = table.NewRow();
table.Rows.Add(r);
r["单位名称"] = "单位名称" + i.ToString();
r["单位代码"] = i;
r["所属单位代码"] = i % 20;
}
this.zbTextBoxSearch1.ItemsSource = new DataView(table);
DataTable table1 = new DataTable();
DataColumn c5 = new DataColumn("所属单位", typeof(string));
DataColumn c4 = new DataColumn("所属单位代码", typeof(int));
table1.Columns.Add(c5);
table1.Columns.Add(c4);
for (int i = 0; i <= 19; i++)
{
DataRow r = table1.NewRow();
table1.Rows.Add(r);
r["所属单位"] = "所属单位" + i.ToString();
r["所属单位代码"] = i;
}
this.zbTextBoxSearch2.ItemsSource = table1.DefaultView;
}
}
}
UserEdit.xaml
<my:ZbWindow x:Class="Zbsoft.OfficeClient.UserEdit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="人员信息修改" Height="482" Width="660"
xmlns:my="clr-namespace:Zbsoft.WpfControls;assembly=Zbsoft.WpfControls" Loaded="ZbWindow_Loaded">
<Grid>
<my:ZbTextBox Height="28" HorizontalAlignment="Left" Margin="91,30,0,0" Name="zbTextBox1"
VerticalAlignment="Top" Width="151" LeftPromptString="姓名" />
<my:ZbTextBox Height="28" HorizontalAlignment="Left" LeftPromptString="用户名"
Margin="91,68,0,0" Name="zbTextBox3" VerticalAlignment="Top" Width="151" />
<my:ZbTextBox Height="28" HorizontalAlignment="Left" Margin="91,265,0,0" Name="zbTextBox2"
VerticalAlignment="Top" Width="151" LeftPromptString="身份证号" />
<my:ZbTextBox Height="28" HorizontalAlignment="Left" LeftPromptString="出生日期"
Margin="91,0,0,230" Name="zbTextBox4" VerticalAlignment="Bottom" Width="151" />
<my:ZbTextBoxSearch Height="26" ValueMember="所属单位代码" DisplayMember="所属单位"
HorizontalAlignment="Left" Margin="66,180,0,0" Name="zbTextBoxSearch2"
SelectedValue="8"
VerticalAlignment="Top" Width="380" LeftPromptString="所属单位" />
<my:ZbTextBoxSearch IsCreateNewEnabled="True" MaxDropDownHeight="200"
EnableNullValue="True" Name="zbTextBoxSearch1"
ValueMember="单位代码" DisplayMember="单位名称"
SelectedValue="{Binding ElementName=zbTextBox1,Path=Text,Mode=TwoWay}"
AutoFiltAddFormatString="所属单位代码={0}"
AutoFiltAddValue="{Binding ElementName=zbTextBoxSearch2,Path=SelectedValue}"
SelectionMode="Extended" BackPromptString="单位" Margin="91,222,56,0"
VerticalAlignment="Top" LeftPromptString="单位" LeftPromptMargin="0,0,2,0"
AutoWidthColumnName="单位名称"
DisplayColumnsHeadText="单位代码,代码"
DisplayColumns="单位名称,单位代码,所属单位代码"
SearchFields="单位代码,单位名称"
RightPromptString="" Height="27">
</my:ZbTextBoxSearch>
<my:ZbDateTime Height="28" HorizontalAlignment="Left" Margin="114,312,0,0" Name="zbDateTime1" VerticalAlignment="Top" Width="111" />
</Grid>
</my:ZbWindow>