2010-11-04 5 views
2

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

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

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

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

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

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

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

+0

Started баунти на этом. Перейдите к редактированию, чтобы предоставить более подробную информацию о реальной проблеме, чтобы узнать, есть ли другое решение. – RationalGeek

ответ

1

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

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

< ScrollViewer х: Name = "ScrollViewer" BorderThickness = "0" Перетяжка = "1 ">

< ItemsPresenter /> 

</ScrollViewer>

это

< ScrollViewer х: Name = "ScrollViewer" BorderThickness = "0" Перетяжка = "1">

< StackPanel x:Name="StackPanel" > 

    < ItemsPresenter /> 

</StackPanel> 

</ScrollViewer>

Это 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>

< ComboBox.ItemsPanel> 

     < ItemsPanelTemplate> 

      < StackPanel> 

      < i:Interaction.EventTriggers> 

       < i:EventTrigger EventName="Loaded"> 

        < RegisterMyInstanceInAccessibleFromCodePlaceAction/> 

       < /i:EventTrigger> 

      < /i:Interaction.EventTriggers> 

      < /StackPanel> 

     < /ItemsPanelTemplate> 

    < /ComboBox.ItemsPanel> 

</ComboBox>

Успехов!

+0

Спасибо за ответ. Я буду играть с этими предложениями завтра и посмотреть, что я могу придумать. – RationalGeek

+1

Спасибо, Владимир. Вы получаете баллы и ответ, потому что это привело меня к реальному решению. Получается, что вам не нужно беспокоиться о вставке StackPanel. Хотя событие KeyDown не срабатывает, когда ComboBox открыт, если вы подклассифицируете элемент управления, вызывается метод OnKeyDown *. Удивительно, но факт. – RationalGeek

5

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

+0

Эта статья предназначена для реализации в WPF, Silverlight не имеет FrameworkElementFactory. – AnthonyWJones

+0

Справа. Извини за это. –

+0

Получил мне правильное решение для другой проблемы, спасибо. – jonas

4

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

<ItemsPanelTemplate> 
    <StackPanel /> 
</ItemsPanelTemplate> 

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

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

ItemsPanelTemplate itemsPanelTemplate = XamlReader.Load("<ItemsPanelTemplate><StackPanel /></ItemsPanelTemplate>"); 
+0

Это может быть на правильном пути для меня. Я не знал о XamlReader. Тем не менее, мне нужно иметь возможность программно получить этот StackPanel, чтобы подписаться на пару событий на нем. Могло ли ваше решение быть изменено, чтобы это можно было сделать? – RationalGeek

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); } 
     } 
    } 

} 
Смежные вопросы