以编程方式为Silverlight ComboBox创建ItemsPanelTemplate?

时间:2010-11-04 21:10:19

标签: silverlight xaml silverlight-4.0

我正在尝试创建与ComboBoxes相关的Blend行为。为了获得我想要的效果,ComboBox的ItemsPanel必须添加一个元素。我不想在每个使用该行为的ComboBox中执行此操作,因此我希望Behavior能够以编程方式注入ItemsPanelTemplate。但是,我似乎找不到办法做到这一点。 ItemsPanelTemplate似乎没有允许我设置可视树的属性/方法。 WPF ItemsPanelTemplate具有VisualTree但Silverlight不具有。

基本上,这个XAML的程序化等价物是什么?

    <ComboBox>
        <ComboBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel/>
            </ItemsPanelTemplate>
        </ComboBox.ItemsPanel>
    </ComboBox>

修改
好吧,显然这不是一个简单的问题,所以我开始了一个赏金,我将提供更多的背景,以防有另一种方式来解决这个问题。我想为Silverlight ComoboBox提供键盘支持。开箱即用它只支持向上和向下箭头,但我也希望它能够工作,这样当用户点击一个字母时,ComboBox会跳转到该字母的第一项,类似于ComboBoxes在浏览器或Windows应用程序中的工作方式

我找到了this blog post,这让我走了一半路。调整该行为代码,ComboBox将根据字母输入更改选择。 然而,它在打开ComboBox时不起作用。根据{{​​3}},其原因在于,当打开ComboBox时,您现在正在与其ItemsPanel进行交互,而不是ComboBox本身。所以根据那篇文章,我实际上需要将一个StackPanel添加到ItemsPanelTemplate并订阅StackPanel的KeyDown事件,以便在打开ComboBox时采取行动。

这就是我的问题,即如何将一个StackPanel放入ComboBox的ItemsPanelTemplate,来自行为。如果这是不可能的,有没有其他方法让这个工作?是的,我知道我可以去应用程序中的每个ComboBox并添加一个StackPanel和事件。但是我想通过一个行为来做到这一点,这样我就不必修改应用程序中的每个ComboBox,因此我可以跨应用程序重用这个逻辑。

下面使用XamlReader的AnthonyWJones回答让我有所帮助,因为我可以创建StackPanel并将其放入模板中。但是,我需要能够以编程方式获取该SP以订阅该事件。

4 个答案:

答案 0 :(得分:6)

这应该有效。我已经展示了如何改变下面的方向。您可以添加其他SetValue调用来修改其他属性。

cb.ItemsPanel = new ItemsPanelTemplate();
var stackPanelFactory = new FrameworkElementFactory(typeof (StackPanel));
// Modify it like this:
stackPanelFactory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal);
// Set the root of the template to the stack panel factory:
cb.ItemsPanel.VisualTree = stackPanelFactory;

您可以在本文中找到更多详细信息:http://www.codeproject.com/KB/WPF/codeVsXAML.aspx

答案 1 :(得分:4)

您实际想要以编程方式构建的内容是: -

<ItemsPanelTemplate>
    <StackPanel />
</ItemsPanelTemplate>

然后,您的行为会将其分配给附加到的ItemsPanel的{​​{1}}属性。目前您的行为是纯代码,但无法在代码中创建上述内容。

由于这是Xaml的一小部分,最简单的方法是使用XamlReader: -

ComboBox

答案 2 :(得分:1)

我认为,最好的方法 - 扩展组合框功能,而不是通过行为而是使用继承。 因此,您可以创建自己的控件MyComboBox:ComboBox。为它创建样式 - 获取默认的ComboBox样式 here

改为编写(按名称查找ScrollViewer):

  

&LT; ScrollViewer x:Name =“ScrollViewer”BorderThickness =“0”Padding =“1”&gt;

   < ItemsPresenter />
     

&LT; / ScrollViewer&gt;

  

&LT; ScrollViewer x:Name =“ScrollViewer”BorderThickness =“0”Padding =“1”&gt;

< StackPanel x:Name="StackPanel" >

   < ItemsPresenter />

< /StackPanel >
     

&LT; / ScrollViewer&gt;

这个StackPanel你可以得到代码:

公共类MyComboBox:ComboBox {

    public CM()
    {
        DefaultStyleKey = typeof (MyComboBox);
    }
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        StackPanel stackPanel = (StackPanel)GetTemplateChild("StackPanel");
        stackPanel.KeyUp += (s, e) => { /*do something*/ };
    }

}

继承更强大。它允许使用模板元素。 如果你决定注入ItemsPanel,你必须明白:

1)在注册面板上保留参考代码是不可能的 2)要获得对注入面板的引用,该面板必须在某些存储器中注册,例如

  

&LT;组合框&GT;

   < ComboBox.ItemsPanel>

       < ItemsPanelTemplate>

           < StackPanel>

             < i:Interaction.EventTriggers>

               < i:EventTrigger EventName="Loaded">

                  < RegisterMyInstanceInAccessibleFromCodePlaceAction/>

               < /i:EventTrigger>

             < /i:Interaction.EventTriggers>

          < /StackPanel>

       < /ItemsPanelTemplate>

   < /ComboBox.ItemsPanel>
     

&LT; /组合框&GT;

祝你好运!

答案 3 :(得分:0)

我在尝试从代码中设置ItemsPanel时找到了您的帖子,以便我可以实现VirtualizingStackPanel。当我的列表中有数百个项目时,性能很糟糕。无论如何..这就是我做到的。

1)自定义控制
2)自定义行为     - 您也可以将此行为应用于普通的ComboBox - 在每个实例或通过样式。

- 您可能还会公开超时值,以便可以在xaml中覆盖..
- 此外,当下拉列表本身打开时,它似乎不起作用。不确定为什么......从未调查过它

1 ..

public class KeyPressSelectionComboBox : ComboBox
{ 

   private BindingExpression _bindingExpression;  


    public KeyPressSelectionComboBox()
        : base()
    {
        Interaction.GetBehaviors(this).Add(new KeyPressSelectionBehavior());
        Height = 22;

        this.SelectionChanged += new SelectionChangedEventHandler(KeyPressSelectionComboBox_SelectionChanged);
    }

    void KeyPressSelectionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (_bindingExpression == null)
        {
            _bindingExpression = this.GetBindingExpression(ComboBox.SelectedValueProperty);
        }
        else
        {
            if (this.GetBindingExpression(ComboBox.SelectedValueProperty) == null)
            {
                this.SetBinding(ComboBox.SelectedValueProperty, _bindingExpression.ParentBinding);
            }
        }
    }  

}

2 ...

/// <summary>
/// This behavior can be attached to a ListBox or ComboBox to 
/// add keyboard selection
/// </summary>
public class KeyPressSelectionBehavior : Behavior<Selector>
{

    private string keyDownHistory = string.Empty;
    private double _keyDownTimeout = 2500;

    private DateTime _lastKeyDownTime;
    private DateTime LastKeyDownTime
    {
        get
        {
            if (this._lastKeyDownTime == null)
                this._lastKeyDownTime = DateTime.Now;

            return this._lastKeyDownTime;
        }
        set { _lastKeyDownTime = value; }
    }


    /// <summary>
    /// Gets or sets the Path used to select the text
    /// </summary>
    public string SelectionMemberPath { get; set; }

    /// <summary>
    /// Gets or sets the Timeout (ms) used to reset the KeyDownHistory item search string
    /// </summary>
    public double KeyDownTimeout
    {
        get { return _keyDownTimeout; }
        set { _keyDownTimeout = value; }
    }


    public KeyPressSelectionBehavior() { }

    /// <summary>
    /// Attaches to the specified object: subscribe on KeyDown event
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.KeyDown += DoKeyDown;
    }

    void DoKeyDown(object sender, KeyEventArgs e)
    {
        // Create a list of strings and indexes
        int index = 0;
        IEnumerable<Item> list = null;

        var path = SelectionMemberPath ??
            this.AssociatedObject.DisplayMemberPath;
        var evaluator = new BindingEvaluator();

        if (path != null)
        {
            list = this.AssociatedObject.Items.OfType<object>()
                .Select(item =>
                {
                    // retrieve the value using the supplied Path
                    var binding = new Binding();
                    binding.Path = new PropertyPath(path);
                    binding.Source = item;

                    BindingOperations.SetBinding(evaluator,
                        BindingEvaluator.TargetProperty, binding);
                    var value = evaluator.Target;

                    return new Item
                    {
                        Index = index++,
                        Text = Convert.ToString(value)
                    };
                });
        }
        else
        {
            list = this.AssociatedObject.Items.OfType<ListBoxItem>()
                .Select(item => new Item
                {
                    Index = index++,
                    Text = Convert.ToString(item.Content)
                });
        }
        // Sort the list starting at next selectedIndex to the end and 
        // then from the beginning
        list = list.OrderBy(item => item.Index <=
            this.AssociatedObject.SelectedIndex ?
            item.Index + this.AssociatedObject.Items.Count : item.Index);

        // calculate how long has passed since the user typed a letter
        var elapsedTime = DateTime.Now - this.LastKeyDownTime;
        if (elapsedTime.TotalMilliseconds <= this.KeyDownTimeout)
        { /* if it's less than the timeout, add to the search string */
            this.keyDownHistory += GetKeyValue(e);
        }
        else
        { /* otherwise replace it */
            this.keyDownHistory = GetKeyValue(e);
        }

        // Find first starting with the search string            
        var searchText = this.keyDownHistory;
        var first = list.FirstOrDefault(item => 
            item.Text.StartsWith(searchText, StringComparison.InvariantCultureIgnoreCase));

        if (first != null)
        { /* found */
            this.AssociatedObject.SelectedIndex = first.Index;
        }
        else
        { /* not found - so reset the KeyDownHistory */
            this.keyDownHistory = string.Empty;
        }


        // reset the last time a key was pressed
        this.LastKeyDownTime = DateTime.Now;
    }

    /// <summary>
    /// Gets the value of the pressed key, 
    /// specifically converting number keys from their "Dx" value to their expected "x" value
    /// </summary>
    /// <param name="e"></param>
    /// <returns></returns>
    private static string GetKeyValue(KeyEventArgs e)
    {
        string rValue = string.Empty;

        switch (e.Key)
        {
            default:
                rValue = e.Key.ToString();
                break;
            case Key.D0:
            case Key.NumPad0:
                rValue = (0).ToString();
                break;
            case Key.D1:
            case Key.NumPad1:
                rValue = (1).ToString();
                break;
            case Key.D2:
            case Key.NumPad2:
                rValue = (2).ToString();
                break;
            case Key.D3:
            case Key.NumPad3:
                rValue = (3).ToString();
                break;
            case Key.D4:
            case Key.NumPad4:
                rValue = (4).ToString();
                break;
            case Key.D5:
            case Key.NumPad5:
                rValue = (5).ToString();
                break;
            case Key.D6:
            case Key.NumPad6:
                rValue = (6).ToString();
                break;
            case Key.D7:
            case Key.NumPad7:
                rValue = (7).ToString();
                break;
            case Key.D8:
            case Key.NumPad8:
                rValue = (8).ToString();
                break;
            case Key.D9:
            case Key.NumPad9:
                rValue = (9).ToString();
                break;

        }

        return rValue;
    }

    /// <summary>
    /// Helper class
    /// </summary>
    private class Item
    {
        public int Index;
        public string Text;
    }

    /// <summary>
    /// Helper class used for property path value retrieval
    /// </summary>
    private class BindingEvaluator : FrameworkElement
    {

        public static readonly DependencyProperty TargetProperty =
            DependencyProperty.Register(
                "Target",
                typeof(object),
                typeof(BindingEvaluator), null);

        public object Target
        {
            get { return GetValue(TargetProperty); }
            set { SetValue(TargetProperty, value); }
        }
    }

}