feat(control): add extended return search for tables and forms
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
using DevExpress.XtraEditors;
|
||||
using DevExpress.XtraGrid;
|
||||
using DevExpress.XtraGrid.Views.Grid;
|
||||
using DevExpress.XtraGrid.Views.Grid.ViewInfo;
|
||||
using DevExpress.Utils;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Lskj.Control
|
||||
{
|
||||
/// <summary>
|
||||
/// 181 扩展搜索专用弹窗。查询文本只存在于本控件中,不参与业务列绑定。
|
||||
/// </summary>
|
||||
internal sealed class ExtendedReturnSearchPopup : UserControl
|
||||
{
|
||||
private readonly TableLayoutPanel searchPanel;
|
||||
private readonly TextEdit searchEdit;
|
||||
private readonly SimpleButton queryButton;
|
||||
private readonly SimpleButton clearButton;
|
||||
private readonly GridControl resultGrid;
|
||||
private readonly GridView resultView;
|
||||
|
||||
public event EventHandler SearchRequested;
|
||||
public event EventHandler ClearRequested;
|
||||
public event EventHandler ResultSelected;
|
||||
|
||||
public ExtendedReturnSearchPopup()
|
||||
{
|
||||
searchPanel = new TableLayoutPanel();
|
||||
searchEdit = new TextEdit();
|
||||
queryButton = new SimpleButton();
|
||||
clearButton = new SimpleButton();
|
||||
resultGrid = new GridControl();
|
||||
resultView = new GridView(resultGrid);
|
||||
|
||||
SuspendLayout();
|
||||
searchPanel.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(searchEdit.Properties)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(resultGrid)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(resultView)).BeginInit();
|
||||
|
||||
searchPanel.Dock = DockStyle.Top;
|
||||
searchPanel.Height = 36;
|
||||
searchPanel.Padding = new Padding(4);
|
||||
searchPanel.ColumnCount = 3;
|
||||
searchPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F));
|
||||
searchPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 72F));
|
||||
searchPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 72F));
|
||||
searchPanel.RowCount = 1;
|
||||
searchPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100F));
|
||||
searchPanel.GrowStyle = TableLayoutPanelGrowStyle.FixedSize;
|
||||
|
||||
queryButton.Dock = DockStyle.Fill;
|
||||
queryButton.Margin = new Padding(4, 0, 0, 0);
|
||||
queryButton.Text = "查询";
|
||||
queryButton.TabIndex = 1;
|
||||
queryButton.Click += QueryButton_Click;
|
||||
|
||||
clearButton.Dock = DockStyle.Fill;
|
||||
clearButton.Margin = new Padding(4, 0, 0, 0);
|
||||
clearButton.Text = "清空";
|
||||
clearButton.TabIndex = 2;
|
||||
clearButton.Click += ClearButton_Click;
|
||||
|
||||
searchEdit.Dock = DockStyle.Fill;
|
||||
searchEdit.Margin = new Padding(0);
|
||||
searchEdit.TabIndex = 0;
|
||||
searchEdit.Properties.AutoHeight = false;
|
||||
searchEdit.Properties.NullValuePrompt = "输入查询内容后按回车";
|
||||
searchEdit.Properties.NullValuePromptShowForEmptyValue = true;
|
||||
searchEdit.KeyDown += SearchEdit_KeyDown;
|
||||
|
||||
searchPanel.Controls.Add(searchEdit, 0, 0);
|
||||
searchPanel.Controls.Add(queryButton, 1, 0);
|
||||
searchPanel.Controls.Add(clearButton, 2, 0);
|
||||
|
||||
resultGrid.Dock = DockStyle.Fill;
|
||||
resultGrid.MainView = resultView;
|
||||
resultGrid.ViewCollection.AddRange(new DevExpress.XtraGrid.Views.Base.BaseView[] { resultView });
|
||||
|
||||
resultView.GridControl = resultGrid;
|
||||
resultView.OptionsBehavior.Editable = false;
|
||||
resultView.OptionsSelection.EnableAppearanceFocusedCell = false;
|
||||
resultView.OptionsView.ShowGroupPanel = false;
|
||||
resultView.OptionsView.ShowIndicator = true;
|
||||
resultView.OptionsView.ColumnAutoWidth = false;
|
||||
resultView.IndicatorWidth = 40;
|
||||
resultView.CustomDrawRowIndicator += ResultView_CustomDrawRowIndicator;
|
||||
resultView.MouseDown += ResultView_MouseDown;
|
||||
resultView.KeyDown += ResultView_KeyDown;
|
||||
|
||||
Controls.Add(resultGrid);
|
||||
Controls.Add(searchPanel);
|
||||
Name = "ExtendedReturnSearchPopup";
|
||||
Size = new System.Drawing.Size(420, 240);
|
||||
|
||||
((System.ComponentModel.ISupportInitialize)(resultView)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(resultGrid)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(searchEdit.Properties)).EndInit();
|
||||
searchPanel.ResumeLayout(false);
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
public GridControl ResultGrid
|
||||
{
|
||||
get { return resultGrid; }
|
||||
}
|
||||
|
||||
public GridView ResultView
|
||||
{
|
||||
get { return resultView; }
|
||||
}
|
||||
|
||||
public string SearchText
|
||||
{
|
||||
get { return (searchEdit.Text ?? string.Empty).Trim(); }
|
||||
}
|
||||
|
||||
public void PrepareForOpen()
|
||||
{
|
||||
searchEdit.Text = string.Empty;
|
||||
resultGrid.DataSource = null;
|
||||
}
|
||||
|
||||
public void FocusSearchEditor()
|
||||
{
|
||||
if (!searchEdit.CanFocus) return;
|
||||
|
||||
searchEdit.Focus();
|
||||
searchEdit.SelectionStart = searchEdit.Text.Length;
|
||||
searchEdit.SelectionLength = 0;
|
||||
}
|
||||
|
||||
private void SearchEdit_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.Down)
|
||||
{
|
||||
e.Handled = true;
|
||||
e.SuppressKeyPress = true;
|
||||
FocusResultRow(false);
|
||||
return;
|
||||
}
|
||||
if (e.KeyCode == Keys.Up)
|
||||
{
|
||||
e.Handled = true;
|
||||
e.SuppressKeyPress = true;
|
||||
FocusResultRow(true);
|
||||
return;
|
||||
}
|
||||
if (e.KeyCode != Keys.Enter) return;
|
||||
|
||||
e.Handled = true;
|
||||
e.SuppressKeyPress = true;
|
||||
RaiseSearchRequested();
|
||||
}
|
||||
|
||||
private void FocusResultRow(bool focusLastRow)
|
||||
{
|
||||
if (resultView.DataRowCount <= 0) return;
|
||||
|
||||
int visibleIndex = focusLastRow ? resultView.DataRowCount - 1 : 0;
|
||||
int rowHandle = resultView.GetVisibleRowHandle(visibleIndex);
|
||||
if (rowHandle < 0) return;
|
||||
|
||||
resultGrid.Focus();
|
||||
resultView.FocusedRowHandle = rowHandle;
|
||||
resultView.SelectRow(rowHandle);
|
||||
}
|
||||
|
||||
private void QueryButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
RaiseSearchRequested();
|
||||
}
|
||||
|
||||
private void RaiseSearchRequested()
|
||||
{
|
||||
if (SearchRequested != null)
|
||||
{
|
||||
SearchRequested(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (ClearRequested != null)
|
||||
{
|
||||
ClearRequested(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResultView_CustomDrawRowIndicator(object sender, RowIndicatorCustomDrawEventArgs e)
|
||||
{
|
||||
if (e.Info == null || !e.Info.IsRowIndicator || e.RowHandle < 0) return;
|
||||
|
||||
e.Appearance.TextOptions.HAlignment = HorzAlignment.Center;
|
||||
e.Info.DisplayText = (e.RowHandle + 1).ToString();
|
||||
}
|
||||
|
||||
private void ResultView_MouseDown(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.Button != MouseButtons.Left) return;
|
||||
|
||||
GridHitInfo hitInfo = resultView.CalcHitInfo(e.Location);
|
||||
if (!hitInfo.InRow && !hitInfo.InRowCell) return;
|
||||
|
||||
resultView.FocusedRowHandle = hitInfo.RowHandle;
|
||||
if (ResultSelected != null)
|
||||
{
|
||||
ResultSelected(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResultView_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.Up &&
|
||||
resultView.FocusedRowHandle == resultView.GetVisibleRowHandle(0))
|
||||
{
|
||||
e.Handled = true;
|
||||
e.SuppressKeyPress = true;
|
||||
FocusSearchEditor();
|
||||
return;
|
||||
}
|
||||
if (e.KeyCode != Keys.Enter) return;
|
||||
|
||||
e.Handled = true;
|
||||
e.SuppressKeyPress = true;
|
||||
if (ResultSelected != null)
|
||||
{
|
||||
ResultSelected(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,816 @@
|
||||
using DevExpress.Utils;
|
||||
using DevExpress.XtraEditors;
|
||||
using DevExpress.XtraEditors.Controls;
|
||||
using DevExpress.XtraGrid.Columns;
|
||||
using Lskj.Business.Impl;
|
||||
using Lskj.Control.Model;
|
||||
using Lskj.Core;
|
||||
using Lskj.Util;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Lskj.Control
|
||||
{
|
||||
/// <summary>
|
||||
/// MyControl 中的 181 扩展搜索控件。业务实际值与界面显示文本相互独立,
|
||||
/// 数据源仅在弹出框打开或用户执行查询时按需访问。
|
||||
/// </summary>
|
||||
public sealed class LabelExtendedReturnSearchEdit : BaseUserControl
|
||||
{
|
||||
private const int MaxRows = 100;
|
||||
private const int DefaultPopupWidth = 520;
|
||||
private const int DefaultPopupHeight = 300;
|
||||
private const int MinimumPopupWidth = 360;
|
||||
|
||||
private readonly Panel labelPanel;
|
||||
private readonly Label titleLabel;
|
||||
private readonly Panel editorPanel;
|
||||
private readonly PopupContainerEdit popupEdit;
|
||||
private readonly PopupContainerControl popupControl;
|
||||
private readonly ExtendedReturnSearchPopup popup;
|
||||
private readonly object schemaSyncRoot = new object();
|
||||
private readonly object querySyncRoot = new object();
|
||||
|
||||
private string actualValue = string.Empty;
|
||||
private string schemaSql = string.Empty;
|
||||
private DataTable schema;
|
||||
private SearchRequest pendingSearch;
|
||||
private bool queryWorkerRunning;
|
||||
private int queryVersion;
|
||||
private bool disposed;
|
||||
|
||||
private sealed class SearchRequest
|
||||
{
|
||||
public int Version;
|
||||
public string SourceSql;
|
||||
public string Keyword;
|
||||
public string ConnectionString;
|
||||
}
|
||||
|
||||
private sealed class QueryResult
|
||||
{
|
||||
public DataTable Table;
|
||||
public Exception Error;
|
||||
}
|
||||
|
||||
public LabelExtendedReturnSearchEdit()
|
||||
{
|
||||
labelPanel = new Panel();
|
||||
titleLabel = new Label();
|
||||
editorPanel = new Panel();
|
||||
popupEdit = new PopupContainerEdit();
|
||||
popupControl = new PopupContainerControl();
|
||||
popup = new ExtendedReturnSearchPopup();
|
||||
|
||||
// MyControl 的坐标和尺寸均来自配置值,保持与现有 LabelTextEdit 一致,
|
||||
// 禁止 UserControl 在高 DPI/大字体环境下再次缩放配置宽度。
|
||||
AutoScaleMode = AutoScaleMode.None;
|
||||
|
||||
SuspendLayout();
|
||||
labelPanel.SuspendLayout();
|
||||
editorPanel.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(popupEdit.Properties)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(popupControl)).BeginInit();
|
||||
popupControl.SuspendLayout();
|
||||
|
||||
labelPanel.BackColor = Color.Transparent;
|
||||
labelPanel.Dock = DockStyle.Left;
|
||||
labelPanel.Width = 40;
|
||||
labelPanel.Controls.Add(titleLabel);
|
||||
|
||||
titleLabel.AutoSize = true;
|
||||
titleLabel.BackColor = Color.Transparent;
|
||||
titleLabel.Location = new Point(5, 4);
|
||||
titleLabel.Text = "名称";
|
||||
|
||||
editorPanel.BackColor = Color.Transparent;
|
||||
editorPanel.Dock = DockStyle.Fill;
|
||||
editorPanel.Controls.Add(popupEdit);
|
||||
|
||||
popupEdit.Dock = DockStyle.Fill;
|
||||
popupEdit.Properties.AutoHeight = false;
|
||||
popupEdit.Properties.AllowNullInput = DefaultBoolean.True;
|
||||
popupEdit.Properties.NullText = string.Empty;
|
||||
popupEdit.Properties.TextEditStyle = TextEditStyles.DisableTextEditor;
|
||||
popupEdit.Properties.PopupSizeable = false;
|
||||
popupEdit.Properties.PopupResizeMode = ResizeMode.Default;
|
||||
popupEdit.Properties.PopupBorderStyle = PopupBorderStyles.Flat;
|
||||
popupEdit.Properties.ShowPopupShadow = true;
|
||||
popupEdit.Properties.PopupControl = popupControl;
|
||||
popupEdit.Properties.Buttons.Clear();
|
||||
popupEdit.Properties.Buttons.Add(new EditorButton(ButtonPredefines.Combo));
|
||||
popupEdit.QueryPopUp += PopupEdit_QueryPopUp;
|
||||
popupEdit.Popup += PopupEdit_Popup;
|
||||
popupEdit.Closed += PopupEdit_Closed;
|
||||
|
||||
popup.Dock = DockStyle.Fill;
|
||||
popupControl.Controls.Add(popup);
|
||||
popup.SearchRequested += Popup_SearchRequested;
|
||||
popup.ClearRequested += Popup_ClearRequested;
|
||||
popup.ResultSelected += Popup_ResultSelected;
|
||||
|
||||
Controls.Add(editorPanel);
|
||||
Controls.Add(labelPanel);
|
||||
Name = "LabelExtendedReturnSearchEdit";
|
||||
Size = new Size(249, 21);
|
||||
Disposed += LabelExtendedReturnSearchEdit_Disposed;
|
||||
|
||||
popupControl.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)(popupControl)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(popupEdit.Properties)).EndInit();
|
||||
editorPanel.ResumeLayout(false);
|
||||
labelPanel.ResumeLayout(false);
|
||||
labelPanel.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
public Model.MyControl ControlObj { get; set; }
|
||||
|
||||
public PopupContainerEdit TextEdit
|
||||
{
|
||||
get { return popupEdit; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存到当前业务字段的实际 ID。
|
||||
/// </summary>
|
||||
public string EditValue
|
||||
{
|
||||
get { return actualValue ?? string.Empty; }
|
||||
set
|
||||
{
|
||||
actualValue = value ?? string.Empty;
|
||||
RefreshDisplayText();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BaseUserControl 的 EditText 仍用于常规赋值入口;读取时返回界面显示文本,
|
||||
/// MyControl.GetControlValue 对 181 会显式读取 EditValue。
|
||||
/// </summary>
|
||||
public override string EditText
|
||||
{
|
||||
get { return popupEdit.Text ?? string.Empty; }
|
||||
set { EditValue = value; }
|
||||
}
|
||||
|
||||
public override string LabelText
|
||||
{
|
||||
get { return titleLabel.Text; }
|
||||
set
|
||||
{
|
||||
titleLabel.Text = value ?? string.Empty;
|
||||
if (FontSize > 0)
|
||||
{
|
||||
labelPanel.AutoSize = false;
|
||||
titleLabel.AutoSize = false;
|
||||
titleLabel.Dock = DockStyle.Fill;
|
||||
titleLabel.TextAlign = ContentAlignment.MiddleLeft;
|
||||
labelPanel.Width = Math.Max(0, (value ?? string.Empty).Length * GetCharWidth());
|
||||
GetCharWidthMultilingual(titleLabel, titleLabel.Text, labelPanel);
|
||||
}
|
||||
else
|
||||
{
|
||||
labelPanel.Width = titleLabel.Width + PaddingLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override float FontSize
|
||||
{
|
||||
get { return base.FontSize; }
|
||||
set
|
||||
{
|
||||
base.FontSize = value;
|
||||
if (value <= 0) return;
|
||||
|
||||
titleLabel.Font = new Font(titleLabel.Font.FontFamily, value);
|
||||
popupEdit.Font = new Font(popupEdit.Font.FontFamily, value);
|
||||
popup.ResultView.Appearance.Row.Font = new Font("微软雅黑", value);
|
||||
}
|
||||
}
|
||||
|
||||
public override string NullText
|
||||
{
|
||||
get { return popupEdit.Properties.NullValuePrompt; }
|
||||
set
|
||||
{
|
||||
popupEdit.Properties.NullValuePromptShowForEmptyValue = !string.IsNullOrEmpty(value);
|
||||
popupEdit.Properties.NullValuePrompt = value ?? string.Empty;
|
||||
base.NullText = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ReadOnly
|
||||
{
|
||||
get { return base.ReadOnly; }
|
||||
set
|
||||
{
|
||||
base.ReadOnly = value;
|
||||
popupEdit.Properties.ReadOnly = value;
|
||||
titleLabel.ForeColor = value
|
||||
? ReadOnlyLabelForceColor
|
||||
: Required ? RequiredLabelForceColor : DefaultLabelForceColor;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Required
|
||||
{
|
||||
get { return base.Required; }
|
||||
set
|
||||
{
|
||||
base.Required = value;
|
||||
if (value) titleLabel.ForeColor = RequiredLabelForceColor;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color BackgroundColor
|
||||
{
|
||||
get { return popupEdit.Properties.Appearance.BackColor; }
|
||||
set { popupEdit.Properties.Appearance.BackColor = value; }
|
||||
}
|
||||
|
||||
public override Color ForeColor
|
||||
{
|
||||
get { return popupEdit.Properties.Appearance.ForeColor; }
|
||||
set { popupEdit.Properties.Appearance.ForeColor = value; }
|
||||
}
|
||||
|
||||
public override bool ContentBold
|
||||
{
|
||||
get { return popupEdit.Properties.Appearance.Font.Bold; }
|
||||
set
|
||||
{
|
||||
Font oldFont = popupEdit.Properties.Appearance.Font;
|
||||
popupEdit.Properties.Appearance.Font = new Font(
|
||||
oldFont.FontFamily,
|
||||
oldFont.Size,
|
||||
value ? FontStyle.Bold : FontStyle.Regular);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsEmpty()
|
||||
{
|
||||
return Model != null && Model.IsEmpty && string.IsNullOrWhiteSpace(EditValue);
|
||||
}
|
||||
|
||||
public override bool IsUpdate()
|
||||
{
|
||||
return Model == null || Model.Text == null ||
|
||||
!Model.Text.Equals(EditValue, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 ResultFields 中 TextMember 对应的业务控件读取预存显示文本。
|
||||
/// </summary>
|
||||
public void RefreshDisplayText()
|
||||
{
|
||||
string displayText = string.Empty;
|
||||
try
|
||||
{
|
||||
string displayField = GetDisplayTargetField();
|
||||
if (ControlObj != null && !string.IsNullOrWhiteSpace(displayField))
|
||||
{
|
||||
BaseUserControl displayControl = ControlObj.FindControl(displayField);
|
||||
if (displayControl != null && !object.ReferenceEquals(displayControl, this))
|
||||
{
|
||||
displayText = displayControl.EditText ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 初始化配置尚未完整时保持为空,弹出时再给出明确配置错误。
|
||||
}
|
||||
popupEdit.EditValue = displayText;
|
||||
}
|
||||
|
||||
private void PopupEdit_QueryPopUp(object sender, System.ComponentModel.CancelEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateBusinessConfiguration();
|
||||
Size popupSize = GetPopupSize();
|
||||
popup.Size = popupSize;
|
||||
popupControl.Size = popupSize;
|
||||
popupEdit.Properties.PopupFormSize = popupSize;
|
||||
popupEdit.Properties.PopupFormMinSize = popupSize;
|
||||
popup.PrepareForOpen();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(EditValue))
|
||||
{
|
||||
StartSearch(EditValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
CancelSearch();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
e.Cancel = true;
|
||||
MessageUtil.Show("扩展搜索配置错误:" + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void PopupEdit_Popup(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
popup.BeginInvoke(new MethodInvoker(popup.FocusSearchEditor));
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void PopupEdit_Closed(object sender, ClosedEventArgs e)
|
||||
{
|
||||
CancelSearch();
|
||||
popup.ResultGrid.DataSource = null;
|
||||
RefreshDisplayText();
|
||||
}
|
||||
|
||||
private void Popup_SearchRequested(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateBusinessConfiguration();
|
||||
string keyword = popup.SearchText;
|
||||
if (string.IsNullOrWhiteSpace(keyword))
|
||||
{
|
||||
CancelSearch();
|
||||
popup.ResultGrid.DataSource = null;
|
||||
return;
|
||||
}
|
||||
StartSearch(keyword);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageUtil.Show("扩展搜索配置错误:" + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void Popup_ClearRequested(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
IList<ExtendedReturnFieldMapping> mappings = ValidateClearConfiguration();
|
||||
List<KeyValuePair<string, object>> values =
|
||||
new List<KeyValuePair<string, object>>();
|
||||
HashSet<string> targetFields =
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
values.Add(new KeyValuePair<string, object>(Model.FieldName, string.Empty));
|
||||
targetFields.Add(Model.FieldName);
|
||||
foreach (ExtendedReturnFieldMapping mapping in mappings)
|
||||
{
|
||||
if (targetFields.Add(mapping.TargetField))
|
||||
{
|
||||
values.Add(new KeyValuePair<string, object>(
|
||||
mapping.TargetField,
|
||||
string.Empty));
|
||||
}
|
||||
}
|
||||
|
||||
ControlObj.ApplyExtendedReturnValues(values);
|
||||
popupEdit.ClosePopup();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageUtil.Show("扩展搜索清空失败:" + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void Popup_ResultSelected(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
DataRow selectedRow = popup.ResultView.GetFocusedDataRow();
|
||||
if (selectedRow == null) return;
|
||||
|
||||
IList<ExtendedReturnFieldMapping> mappings = ValidateBusinessConfiguration();
|
||||
ValidateSourceColumns(selectedRow.Table.Columns, mappings);
|
||||
|
||||
List<KeyValuePair<string, object>> values = new List<KeyValuePair<string, object>>();
|
||||
foreach (ExtendedReturnFieldMapping mapping in mappings)
|
||||
{
|
||||
KeyValuePair<string, object> mappedValue = new KeyValuePair<string, object>(
|
||||
mapping.TargetField,
|
||||
selectedRow[mapping.SourceField]);
|
||||
if (string.Equals(mapping.TargetField, Model.FieldName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
values.Insert(0, mappedValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
values.Add(mappedValue);
|
||||
}
|
||||
}
|
||||
|
||||
bool containsActualField = mappings.Any(item =>
|
||||
string.Equals(item.TargetField, Model.FieldName, StringComparison.OrdinalIgnoreCase));
|
||||
if (!containsActualField)
|
||||
{
|
||||
values.Insert(0, new KeyValuePair<string, object>(
|
||||
Model.FieldName,
|
||||
selectedRow[Model.ValueMember]));
|
||||
}
|
||||
|
||||
ApplyValues(values);
|
||||
RefreshDisplayText();
|
||||
popupEdit.ClosePopup();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageUtil.Show("扩展搜索返回值失败:" + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyValues(IList<KeyValuePair<string, object>> values)
|
||||
{
|
||||
ControlObj.ApplyExtendedReturnValues(values);
|
||||
}
|
||||
|
||||
private IList<ExtendedReturnFieldMapping> ValidateClearConfiguration()
|
||||
{
|
||||
if (Model == null) throw new InvalidOperationException("扩展返回控件配置不存在。");
|
||||
if (ControlObj == null) throw new InvalidOperationException("扩展返回控件未关联 MyControl。");
|
||||
|
||||
IList<ExtendedReturnFieldMapping> mappings =
|
||||
ExtendedReturnSupport.ParseResultFields(Model.ResultFields);
|
||||
if (mappings.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("resultfields 不能为空。");
|
||||
}
|
||||
if (ControlObj.FindControl(Model.FieldName) == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format("MyControl 中不存在业务控件“{0}”。", Model.FieldName));
|
||||
}
|
||||
foreach (ExtendedReturnFieldMapping mapping in mappings)
|
||||
{
|
||||
if (ControlObj.FindControl(mapping.TargetField) == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format("MyControl 中不存在业务控件“{0}”。", mapping.TargetField));
|
||||
}
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
private IList<ExtendedReturnFieldMapping> ValidateBusinessConfiguration()
|
||||
{
|
||||
if (Model == null) throw new InvalidOperationException("扩展返回控件配置不存在。");
|
||||
if (ControlObj == null) throw new InvalidOperationException("扩展返回控件未关联 MyControl。");
|
||||
if (string.IsNullOrWhiteSpace(Model.SourceSql))
|
||||
{
|
||||
throw new InvalidOperationException("搜索数据源 SQL 不能为空。");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Model.ValueMember))
|
||||
{
|
||||
throw new InvalidOperationException("fieldsqlid 不能为空。");
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(Model.TextMember))
|
||||
{
|
||||
throw new InvalidOperationException("fieldsqlname 不能为空。");
|
||||
}
|
||||
if (string.Equals(Model.ValueMember, Model.TextMember, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("fieldsqlid 与 fieldsqlname 必须配置为不同的返回列。");
|
||||
}
|
||||
|
||||
IList<ExtendedReturnFieldMapping> mappings = ExtendedReturnSupport.ParseResultFields(Model.ResultFields);
|
||||
if (mappings.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("resultfields 不能为空。");
|
||||
}
|
||||
|
||||
int displayMappingCount = mappings.Count(item =>
|
||||
string.Equals(item.SourceField, Model.TextMember, StringComparison.OrdinalIgnoreCase));
|
||||
if (displayMappingCount == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format("resultfields 未配置显示字段“{0}”的业务控件映射。", Model.TextMember));
|
||||
}
|
||||
if (displayMappingCount > 1)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format("resultfields 中显示字段“{0}”只能映射到一个业务控件。", Model.TextMember));
|
||||
}
|
||||
|
||||
ExtendedReturnFieldMapping actualMapping = mappings.FirstOrDefault(item =>
|
||||
string.Equals(item.TargetField, Model.FieldName, StringComparison.OrdinalIgnoreCase));
|
||||
if (actualMapping != null &&
|
||||
!string.Equals(actualMapping.SourceField, Model.ValueMember, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format("实际值控件“{0}”必须映射到 fieldsqlid 指定的返回列“{1}”。",
|
||||
Model.FieldName,
|
||||
Model.ValueMember));
|
||||
}
|
||||
|
||||
foreach (ExtendedReturnFieldMapping mapping in mappings)
|
||||
{
|
||||
if (ControlObj.FindControl(mapping.TargetField) == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format("MyControl 中不存在业务控件“{0}”。", mapping.TargetField));
|
||||
}
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
private void ValidateSourceColumns(
|
||||
DataColumnCollection columns,
|
||||
IEnumerable<ExtendedReturnFieldMapping> mappings)
|
||||
{
|
||||
if (columns == null || columns.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("扩展搜索数据源没有返回任何列。");
|
||||
}
|
||||
if (!columns.Contains(Model.ValueMember))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format("返回数据不存在值字段“{0}”。", Model.ValueMember));
|
||||
}
|
||||
if (!columns.Contains(Model.TextMember))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format("返回数据不存在显示字段“{0}”。", Model.TextMember));
|
||||
}
|
||||
foreach (ExtendedReturnFieldMapping mapping in mappings)
|
||||
{
|
||||
if (!columns.Contains(mapping.SourceField))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
string.Format("返回数据不存在映射源字段“{0}”。", mapping.SourceField));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetDisplayTargetField()
|
||||
{
|
||||
if (Model == null) return string.Empty;
|
||||
IList<ExtendedReturnFieldMapping> mappings = ExtendedReturnSupport.ParseResultFields(Model.ResultFields);
|
||||
return ExtendedReturnSupport.FindTargetField(mappings, Model.TextMember);
|
||||
}
|
||||
|
||||
private Size GetPopupSize()
|
||||
{
|
||||
int width = Model != null && Model.LookUpWidth > 0
|
||||
? Model.LookUpWidth
|
||||
: DefaultPopupWidth;
|
||||
width = Math.Max(width, MinimumPopupWidth);
|
||||
return new Size(width, DefaultPopupHeight);
|
||||
}
|
||||
|
||||
private string BuildSourceSql()
|
||||
{
|
||||
string sourceSql = Model.SourceSql;
|
||||
if (ControlObj != null)
|
||||
{
|
||||
sourceSql = ControlObj.ReplaceControlValue(sourceSql);
|
||||
}
|
||||
sourceSql = BaseImpl.GetDefaultValue(sourceSql);
|
||||
return ReplaceHelper.ReplaceParam(sourceSql);
|
||||
}
|
||||
|
||||
private void StartSearch(string keyword)
|
||||
{
|
||||
SearchRequest request = new SearchRequest();
|
||||
request.Version = Interlocked.Increment(ref queryVersion);
|
||||
request.SourceSql = BuildSourceSql();
|
||||
request.Keyword = keyword;
|
||||
request.ConnectionString = SqlHelper._connection.ConnectionString;
|
||||
|
||||
bool startWorker = false;
|
||||
lock (querySyncRoot)
|
||||
{
|
||||
pendingSearch = request;
|
||||
if (!queryWorkerRunning)
|
||||
{
|
||||
queryWorkerRunning = true;
|
||||
startWorker = true;
|
||||
}
|
||||
}
|
||||
if (startWorker)
|
||||
{
|
||||
ThreadPool.QueueUserWorkItem(ProcessSearchQueue);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSearchQueue(object state)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
SearchRequest request;
|
||||
lock (querySyncRoot)
|
||||
{
|
||||
request = pendingSearch;
|
||||
pendingSearch = null;
|
||||
if (request == null)
|
||||
{
|
||||
queryWorkerRunning = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QueryResult result = Query(
|
||||
request.SourceSql,
|
||||
request.Keyword,
|
||||
request.ConnectionString);
|
||||
DeliverResult(request.Version, result);
|
||||
}
|
||||
}
|
||||
|
||||
private QueryResult Query(string sourceSql, string keyword, string connectionString)
|
||||
{
|
||||
QueryResult result = new QueryResult();
|
||||
try
|
||||
{
|
||||
DataTable sourceSchema = GetSchema(sourceSql, connectionString);
|
||||
IList<ExtendedReturnFieldMapping> mappings = ExtendedReturnSupport.ParseResultFields(Model.ResultFields);
|
||||
ValidateSourceColumns(sourceSchema.Columns, mappings);
|
||||
string searchSql = ExtendedReturnSupport.BuildSearchSql(sourceSql, sourceSchema.Columns, MaxRows);
|
||||
result.Table = ExecuteQuery(
|
||||
searchSql,
|
||||
ExtendedReturnSupport.BuildLikeParameterValue(keyword),
|
||||
connectionString);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Error = ex;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private DataTable GetSchema(string sourceSql, string connectionString)
|
||||
{
|
||||
lock (schemaSyncRoot)
|
||||
{
|
||||
if (schema != null && string.Equals(schemaSql, sourceSql, StringComparison.Ordinal))
|
||||
{
|
||||
return schema;
|
||||
}
|
||||
|
||||
schema = ExecuteQuery(ExtendedReturnSupport.BuildStructureSql(sourceSql), null, connectionString);
|
||||
schemaSql = sourceSql;
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
|
||||
private static DataTable ExecuteQuery(
|
||||
string commandText,
|
||||
string keywordParameterValue,
|
||||
string connectionString)
|
||||
{
|
||||
DataTable table = new DataTable();
|
||||
using (DbConnection connection = SqlHelper.dbFactory.CreateConnection())
|
||||
using (DbCommand command = SqlHelper.dbFactory.CreateCommand())
|
||||
using (DbDataAdapter adapter = SqlHelper.dbFactory.CreateDataAdapter())
|
||||
{
|
||||
connection.ConnectionString = connectionString;
|
||||
command.Connection = connection;
|
||||
command.CommandText = commandText;
|
||||
command.CommandType = CommandType.Text;
|
||||
command.CommandTimeout = SqlHelper.CommandTimeout;
|
||||
|
||||
if (keywordParameterValue != null)
|
||||
{
|
||||
DbParameter parameter = SqlHelper.dbFactory.CreateParameter();
|
||||
parameter.ParameterName = ExtendedReturnSupport.SearchParameterName;
|
||||
parameter.DbType = DbType.String;
|
||||
parameter.Size = 4000;
|
||||
parameter.Value = keywordParameterValue;
|
||||
command.Parameters.Add(parameter);
|
||||
}
|
||||
|
||||
adapter.SelectCommand = command;
|
||||
connection.Open();
|
||||
adapter.Fill(table);
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
private void DeliverResult(int version, QueryResult result)
|
||||
{
|
||||
if (disposed || !IsHandleCreated) return;
|
||||
|
||||
MethodInvoker deliver = new MethodInvoker(delegate
|
||||
{
|
||||
if (disposed || IsDisposed || version != queryVersion || !popupEdit.IsPopupOpen) return;
|
||||
if (result.Error != null)
|
||||
{
|
||||
MessageUtil.Show("扩展搜索查询失败:" + result.Error.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
popup.ResultGrid.DataSource = result.Table;
|
||||
ConfigureResultColumns(result.Table);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
BeginInvoke(deliver);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void ConfigureResultColumns(DataTable table)
|
||||
{
|
||||
if (table == null) return;
|
||||
|
||||
IList<ExtendedReturnFieldMapping> mappings = ExtendedReturnSupport.ParseResultFields(Model.ResultFields);
|
||||
popup.ResultView.Columns.Clear();
|
||||
foreach (DataColumn dataColumn in table.Columns)
|
||||
{
|
||||
GridColumn column = new GridColumn();
|
||||
column.Name = column.FieldName = dataColumn.ColumnName;
|
||||
column.Caption = GetColumnCaption(dataColumn.ColumnName, mappings);
|
||||
column.Visible = !dataColumn.ColumnName.StartsWith("_", StringComparison.Ordinal);
|
||||
popup.ResultView.Columns.Add(column);
|
||||
}
|
||||
|
||||
string[] configuredWidths = string.IsNullOrWhiteSpace(Model.LookUpFieldsWidth)
|
||||
? null
|
||||
: Model.LookUpFieldsWidth.Trim().TrimEnd(',').Split(',');
|
||||
int visibleIndex = 0;
|
||||
foreach (GridColumn column in popup.ResultView.Columns)
|
||||
{
|
||||
if (!column.Visible) continue;
|
||||
|
||||
int width;
|
||||
if (configuredWidths != null && visibleIndex < configuredWidths.Length &&
|
||||
int.TryParse(configuredWidths[visibleIndex], out width))
|
||||
{
|
||||
column.Width = width;
|
||||
}
|
||||
else
|
||||
{
|
||||
int captionWidth = GraphicsText.GetTextWidth(column.Caption);
|
||||
int valueWidth = CalcMaxColumnWidth(table, column.FieldName);
|
||||
column.Width = Math.Max(captionWidth, valueWidth);
|
||||
}
|
||||
visibleIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetColumnCaption(
|
||||
string sourceField,
|
||||
IEnumerable<ExtendedReturnFieldMapping> mappings)
|
||||
{
|
||||
string targetField = ExtendedReturnSupport.FindTargetField(mappings, sourceField);
|
||||
if (string.IsNullOrWhiteSpace(targetField)) return sourceField;
|
||||
|
||||
BaseUserControl targetControl = ControlObj.FindControl(targetField);
|
||||
if (targetControl != null && !string.IsNullOrWhiteSpace(targetControl.LabelText))
|
||||
{
|
||||
return targetControl.LabelText;
|
||||
}
|
||||
return targetField;
|
||||
}
|
||||
|
||||
private static int CalcMaxColumnWidth(DataTable table, string fieldName)
|
||||
{
|
||||
int maxWidth = 0;
|
||||
foreach (DataRow row in table.Rows)
|
||||
{
|
||||
object value = row[fieldName];
|
||||
int width = GraphicsText.GetTextWidth(
|
||||
value == null || value == DBNull.Value ? string.Empty : value + string.Empty);
|
||||
if (width > maxWidth) maxWidth = width;
|
||||
}
|
||||
return maxWidth;
|
||||
}
|
||||
|
||||
private void CancelSearch()
|
||||
{
|
||||
Interlocked.Increment(ref queryVersion);
|
||||
lock (querySyncRoot)
|
||||
{
|
||||
pendingSearch = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void LabelExtendedReturnSearchEdit_Disposed(object sender, EventArgs e)
|
||||
{
|
||||
disposed = true;
|
||||
CancelSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user