• 实现下拉列表支持DataGrid的AutoCompleteBox


    Silverlight中的AutoCompleteBox是一个非常强大的输入控件,可以实现灵活的参照录入。如果参照内容具有多个属性,则下拉列表使用多列的DataGrid是一个比较好的选择。

    依靠控件模板强大的功能,AutoCompleteBox是可以实现这个需求的,并在Silverlight ToolKit Samples(November 2009)中给出了一个示例:AutoCompleteBox widht a DataGrid DropDown。示例中使用了一个从DataGrid继承的自定义的选择适配器(实现ISelectionAdapter接口),并最终用自定义AutoCompleteBox的控件模板实现。

    从示例的实现过程可以看出,实现这个功能的工作量还是相当大的,如果项目中多处有多该功能的需求,则工作量几乎是不可接受的。于是想到了创建模板化控件。。。

    1、首先,在项目中添加Silverlight模板化控件,取名为:AutoCompleteBoxPlus。

    添加以后,VS自动为我们生成了一个从Control继承的类,还有一个Themes文件夹下的Generic.xaml。

    2、修改AutoCompleteBoxPlus的基类,由Control改为AutoCompleteBox。

    3、为AutoCompleteBoxPlus定义两个属性:PopupWidth(下拉列表宽度)及PopupColumns(下拉列表列集合)。本来是下拉的,不知自己为何用了Popup这个单词,呵呵。

    至此,AutoCompleteBoxPlus类基本完成,完整代码如下:

        /// <summary>
        /// 自动完成框扩展,支持下拉列表中使用DataGrid
        /// </summary>
        public class AutoCompleteBoxPlus : AutoCompleteBox
        {
            public AutoCompleteBoxPlus()
            {
                this.DefaultStyleKey = typeof(AutoCompleteBoxPlus);
    
                this.PopupWidth = this.Width;
            }
    
            private ObservableCollection<DataGridColumn> popupColumns = new ObservableCollection<DataGridColumn>();
    
            public static readonly DependencyProperty PopupWidthProperty =
                DependencyProperty.Register("PopupWidth", typeof(double), typeof(AutoCompleteBoxPlus), null);
    
            /// <summary>
            /// 下拉列表宽度。
            /// </summary>
            public double PopupWidth
            {
                get { return (double)GetValue(PopupWidthProperty); }
                set { SetValue(PopupWidthProperty, value); }
            }
    
            public override void OnApplyTemplate()
            {
                base.OnApplyTemplate();
    
                DataGrid dg = this.SelectionAdapter as DataGrid;
                foreach (var column in this.PopupColumns) dg.Columns.Add(column);
            }
    
            /// <summary>
            /// 下拉列表列集合。
            /// </summary>
            public ObservableCollection<DataGridColumn> PopupColumns
            {
                get { return this.popupColumns; }
            }
        }

    4、在Generic.xaml中定义AutoCompleteBoxPlus控件的模板,模板大部分采用了Silverlight ToolKit Samples中的代码。

    <ResourceDictionary
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Demo">
        
        <Style TargetType="local:AutoCompleteBoxPlus">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="local:AutoCompleteBoxPlus">
                        <Grid>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="PopupStates">
                                    <VisualStateGroup.Transitions>
                                        <VisualTransition GeneratedDuration="0:0:0.2" To="PopupOpened" />
                                        <VisualTransition GeneratedDuration="0:0:0.5" To="PopupClosed" />
                                    </VisualStateGroup.Transitions>
                                    <VisualState x:Name="PopupOpened">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="PopupBorder" Storyboard.TargetProperty="Opacity" To="1.0" />
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="PopupClosed">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="PopupBorder" Storyboard.TargetProperty="Opacity" To="0.0" />
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <TextBox IsTabStop="True" x:Name="Text" Margin="0"/>
                            <Popup x:Name="Popup">
                                <Border x:Name="PopupBorder" HorizontalAlignment="Stretch" Opacity="0.0" BorderThickness="0"
                                        CornerRadius="3">
                                    <local:DataGridSelectionAdapter x:Name="SelectionAdapter" AutoGenerateColumns="False" 
                                        IsReadOnly="True" HorizontalContentAlignment="Left" 
                                        Width="{TemplateBinding PopupWidth}" >
                                    </local:DataGridSelectionAdapter>
                                </Border>
                            </Popup>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>

    需注意的是,模板中用到了一个自定义的类:DataGridSelectionAdapter,即上文介绍示例时提到的选择适配器了。该类直接从Silverlight ToolKit Samples中Reflector,然后略加修改。完整代码如下:

        /// <summary>
        /// DataGrid选择适配器。
        /// </summary>
        public class DataGridSelectionAdapter : DataGrid, ISelectionAdapter
        {
            public DataGridSelectionAdapter()
            {
                base.SelectionChanged += new SelectionChangedEventHandler(this.OnSelectionChanged);
                MouseLeftButtonUp += new MouseButtonEventHandler(this.OnSelectorMouseLeftButtonUp);
            }
    
            // Properties
            private bool IgnoreAnySelection { get; set; }
    
            private bool IgnoringSelectionChanged { get; set; }
    
            // Events
            public event RoutedEventHandler Cancel;
    
            public event RoutedEventHandler Commit;
    
            public new event SelectionChangedEventHandler SelectionChanged;
    
            private void AfterAdapterAction()
            {
                this.IgnoringSelectionChanged = true;
                this.SelectedItem = null;
                SelectedIndex = -1;
                this.IgnoringSelectionChanged = false;
                this.IgnoreAnySelection = true;
            }
    
            public AutomationPeer CreateAutomationPeer()
            {
                return new DataGridAutomationPeer(this);
            }
    
            public void HandleKeyDown(KeyEventArgs e)
            {
                Key key = e.Key;
                if (key != Key.Enter)
                {
                    switch (key)
                    {
                        case Key.Up:
                            this.IgnoreAnySelection = false;
                            this.SelectedIndexDecrement();
                            e.Handled = true;
                            return;
    
                        case Key.Right:
                            return;
    
                        case Key.Down:
                            if ((ModifierKeys.Alt & Keyboard.Modifiers) == ModifierKeys.None)
                            {
                                this.IgnoreAnySelection = false;
                                this.SelectedIndexIncrement();
                                e.Handled = true;
                            }
                            return;
    
                        case Key.Escape:
                            this.OnCancel(this, e);
                            e.Handled = true;
                            return;
                    }
                }
                else
                {
                    this.OnCommit(this, e);
                    e.Handled = true;
                }
            }
    
            private void OnCancel(object sender, RoutedEventArgs e)
            {
                RoutedEventHandler cancel = this.Cancel;
                if (cancel != null)
                {
                    cancel(sender, e);
                }
                this.AfterAdapterAction();
            }
    
            private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                this.IgnoreAnySelection = true;
            }
    
            private void OnCommit(object sender, RoutedEventArgs e)
            {
                RoutedEventHandler commit = this.Commit;
                if (commit != null)
                {
                    commit(sender, e);
                }
                this.AfterAdapterAction();
            }
    
            private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                if (!this.IgnoringSelectionChanged && !this.IgnoreAnySelection)
                {
                    SelectionChangedEventHandler selectionChanged = this.SelectionChanged;
                    if (selectionChanged != null)
                    {
                        selectionChanged(sender, e);
                    }
                }
            }
    
            private void OnSelectorMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
            {
                this.IgnoreAnySelection = false;
                this.OnSelectionChanged(this, null);
                this.OnCommit(this, new RoutedEventArgs());
            }
    
            private void SelectedIndexDecrement()
            {
                int selectedIndex = SelectedIndex;
                if (selectedIndex >= 0)
                {
                    SelectedIndex--;
                }
                else if (selectedIndex == -1)
                {
                    SelectedIndex = this.Items.Count - 1;
                }
                ScrollIntoView(this.SelectedItem, Columns[0]);
            }
    
            private void SelectedIndexIncrement()
            {
                SelectedIndex = ((SelectedIndex + 1) >= this.Items.Count) ? -1 : (SelectedIndex + 1);
                ScrollIntoView(this.SelectedItem, Columns[0]);
            }
    
    
            private ObservableCollection<object> Items
            {
                get
                {
                    return (this.ItemsSource as ObservableCollection<object>);
                }
            }
    
            public new IEnumerable ItemsSource
            {
                get
                {
                    return base.ItemsSource;
                }
                set
                {
                    INotifyCollectionChanged itemsSource;
                    if (base.ItemsSource != null)
                    {
                        itemsSource = base.ItemsSource as INotifyCollectionChanged;
                        if (itemsSource != null)
                        {
                            itemsSource.CollectionChanged -= new NotifyCollectionChangedEventHandler(this.OnCollectionChanged);
                        }
                    }
                    base.ItemsSource = value;
                    if (base.ItemsSource != null)
                    {
                        itemsSource = base.ItemsSource as INotifyCollectionChanged;
                        if (itemsSource != null)
                        {
                            itemsSource.CollectionChanged += new NotifyCollectionChangedEventHandler(this.OnCollectionChanged);
                        }
                    }
                }
            }
    
            public new object SelectedItem
            {
                get
                {
                    return base.SelectedItem;
                }
                set
                {
                    this.IgnoringSelectionChanged = true;
                    base.SelectedItem = value;
                    this.IgnoringSelectionChanged = false;
                }
            }
        }

    至此,一个支持DataGrid下拉列表的AutoCompleteBox控件就实现了。使用示例代码:

    <local:AutoCompleteBoxPlus x:Name="acbp" ValueMemberPath="Name">
           <local:AutoCompleteBoxPlus.PopupColumns>                
                <data:DataGridTextColumn Header="Code" Binding="{Binding Code}" Width="80" />
                <data:DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="120" />
           </local:AutoCompleteBoxPlus.PopupColumns>
    </local:AutoCompleteBoxPlus>

    不过在实现过程中也着实遇到了一些问题,记录一下权作备忘:

    A、自定义类中可以使用控件模板中的命名控件,但只能在重写的OnApplyTemplate方法中用GetTemplateChild方法获取,在不正确的时机或FindName方法是无法获取到的。

    B、重写OnApplyTemplate方法时必须调用基类的该方法。

    C、自定义的选择适配器必须命名为SelectionAdapter。全部AutoCompleteBox 控件的命名的部件可参见Silverlight文档中的“AutoCompleteBox 样式和模板”。

  • 相关阅读:
    错误C2665: “AfxMessageBox”: 2 个重载中没有一个可以转换所有参数类型
    为什么DW的可视化下看到的效果与浏览器的效果有所区别?
    font-family:黑体;导致css定义全部不起作用
    web标准中定义id与class有什么区别吗
    网页尺寸规范
    SEO为什么要求网页设计师用DIV+CSS布局网页?
    去掉CSS赘余代码,CSS可以更简洁
    解决IE6、IE7、Firefox兼容最简单的CSS Hack
    实战中总结出来的CSS常见问题及解决办法
    高效整洁CSS代码原则 (下)
  • 原文地址:https://www.cnblogs.com/chinadhf/p/1717431.html
Copyright © 2020-2023  润新知