Программно создать ItemsPanelTemplate для Silverlight ComboBox? - PullRequest
2 голосов
/ 05 ноября 2010

Я пытаюсь создать поведение Blend, связанное с ComboBox. Чтобы получить желаемый эффект, в ItemsPanel ComboBox должен быть добавлен определенный элемент. Я не хочу делать это в каждом ComboBox, который использует поведение, поэтому я хочу, чтобы Поведение могло программно внедрять ItemsPanelTemplate. Тем не менее, я не могу найти способ сделать это. ItemsPanelTemplate, похоже, не имеет свойства / метода, который позволяет мне устанавливать визуальное дерево. WPF ItemsPanelTemplate имеет VisualTree, а Silverlight - нет.

В принципе, что является программным эквивалентом этого XAML?

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

Edit:
Хорошо, по-видимому, это не простой вопрос, поэтому я учредил награду и собираюсь дать дополнительную информацию на случай, если есть другой способ сделать это. Я хочу обеспечить поддержку клавиатуры для Silverlight ComoboBox. Из коробки он поддерживает только стрелки вверх и вниз, но я также хочу, чтобы он работал так, чтобы, когда пользователь нажимал на букву, ComboBox переходил к первому элементу этой буквы, подобно тому, как ComboBox работает в браузере или приложении Windows. .

Я нашел это сообщение в блоге , которое дало мне половину пути. Адаптируя этот код поведения, ComboBox будет изменять выбор в зависимости от ввода букв. Однако не работает при открытии ComboBox. Причина этого, согласно этого сообщения в блоге , заключается в том, что при открытии ComboBox вы теперь взаимодействуете с его ItemsPanel, а не с самим ComboBox. Таким образом, согласно этому посту, мне действительно нужно добавить StackPanel к ItemsPanelTemplate и подписаться на событие KeyDown StackPanel, чтобы действовать при открытии ComboBox.

Так вот что вызвало мой вопрос о том, как получить StackPanel в шаблон ItemsPanel ComboBox, из поведения . Если это невозможно, есть ли альтернативные способы заставить это работать? Да, я знаю, что могу перейти к каждому ComboBox в приложении и добавить StackPanel и событие. Но я хочу сделать это с помощью поведения, чтобы мне не приходилось изменять каждый ComboBox в приложении и чтобы я мог повторно использовать эту логику в приложениях.

Ответ AnthonyWJones, приведенный ниже с использованием XamlReader, частично помогает мне: я могу создать StackPanel и вставить его в шаблон. Тем не менее, я должен быть в состоянии добраться до этого SP программно, чтобы подписаться на событие.

Ответы [ 4 ]

6 голосов
/ 05 ноября 2010

Это должно работать. Я показал, как вы можете изменить ориентацию ниже. Вы можете добавить дополнительные вызовы 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

4 голосов
/ 05 ноября 2010

То, что вы действительно хотите построить программно, это: -

<ItemsPanelTemplate>
    <StackPanel />
</ItemsPanelTemplate>

Затем ваше поведение присвоит это свойству ItemsPanel того ComboBox, к которому оно прикреплено. В настоящее время ваше поведение является чистым кодом, но нет способа создать вышесказанное исключительно в коде.

Поскольку это небольшой фрагмент для Xaml, самый простой подход - использовать XamlReader: -

ItemsPanelTemplate itemsPanelTemplate = XamlReader.Load("<ItemsPanelTemplate><StackPanel /></ItemsPanelTemplate>");
1 голос
/ 10 ноября 2010

Я думаю, лучший способ для вас - расширить функциональность комбинированного списка не через поведение, а с помощью наследования. Итак, вы можете создать собственный элемент управления MyComboBox: ComboBox. Создайте для него стиль - получите стиль ComboBox по умолчанию здесь

И вместо этого напишите (ищите ScrollViewer по имени):

   < ItemsPresenter />

это

< StackPanel x:Name="StackPanel" >

   < ItemsPresenter />

< /StackPanel >

Эта 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) чтобы получить ссылку на вставленную панель, эта панель должна быть зарегистрирована в некотором хранилище, например,

   < ComboBox.ItemsPanel>

       < ItemsPanelTemplate>

           < StackPanel>

             < i:Interaction.EventTriggers>

               < i:EventTrigger EventName="Loaded">

                  < RegisterMyInstanceInAccessibleFromCodePlaceAction/>

               < /i:EventTrigger>

             < /i:Interaction.EventTriggers>

          < /StackPanel>

       < /ItemsPanelTemplate>

   < /ComboBox.ItemsPanel>

Удачи!

0 голосов
/ 27 октября 2011

Я нашел ваше сообщение при попытке установить 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); }
        }
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...