2010-11-04 7 views
2

J'essaye de créer un comportement de mélange lié aux ComboBoxes. Afin d'obtenir l'effet que je veux, le ItemsPanel de la ComboBox doit avoir un certain élément ajouté. Je ne veux pas faire cela dans chaque ComboBox qui utilise le comportement, donc je veux que le Comportement puisse injecter le ItemsPanelTemplate par programmation. Cependant, je n'arrive pas à trouver un moyen de le faire. ItemsPanelTemplate ne semble pas avoir une propriété/méthode qui me permet de définir l'arborescence visuelle. WPF ItemsPanelTemplate a le VisualTree mais Silverlight ne le fait pas.Créer par programme ItemsPanelTemplate pour Silverlight ComboBox?

Fondamentalement, quel est l'équivalent programmatique de ce XAML?

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

Edit:
Bon apparemment, ce n'est pas une question facile, alors j'ai commencé une prime et je vais donner un peu plus de fond dans le cas où il y a une autre façon d'aller à ce sujet. Je veux fournir un support clavier pour la ComoboBox Silverlight. Une fois sorti de la boîte, il ne supporte que les flèches haut et bas, mais je veux aussi qu'il fonctionne de sorte que lorsque l'utilisateur frappe une lettre, la ComboBox saute au premier élément de cette lettre, similaire au ComboBox fonctionne dans un navigateur ou une application Windows .

J'ai trouvé this blog post, ce qui m'a permis de faire la moitié du chemin. En adaptant ce code de comportement, la ComboBox changera la sélection en fonction de la saisie de lettre. Cependant, cela ne fonctionne pas lorsque le ComboBox est ouvert. La raison pour cela, selon this blog post est que lorsque le ComboBox est ouvert, vous êtes maintenant en interaction avec son ItemsPanel et non le ComboBox lui-même. Donc, selon ce post, j'ai besoin d'ajouter un StackPanel au ItemsPanelTemplate et de m'abonner à l'événement KeyDown de StackPanel, afin de prendre des mesures lorsque le ComboBox est ouvert.

Voilà donc ce qui m'a conduit à demander comment obtenir un StackPanel dans le ItemsPanelTemplate d'un ComboBox, à partir d'un comportement. Si ce n'est pas possible, y a-t-il d'autres moyens de faire en sorte que cela fonctionne? Oui, je sais que je pourrais aller à chaque ComboBox dans l'application et ajouter un StackPanel et l'événement. Mais je veux le faire par le biais d'un comportement afin que je n'ai pas à modifier chaque ComboBox dans l'application, et ainsi je peux réutiliser cette logique à travers les applications.

La réponse d'AnthonyWJones ci-dessous à l'aide de XamlReader m'amène à me rendre compte que je peux créer le StackPanel et l'intégrer au modèle. Cependant, je dois être en mesure d'obtenir ce SP par programme afin de m'abonner à l'événement.

+0

Commencer une prime à ce sujet. Aller à modifier pour fournir plus de détails sur le problème réel pour voir s'il existe une autre solution. – RationalGeek

Répondre

1

Je pense, la meilleure façon pour vous - étendre les fonctionnalités combobox pas par le comportement, mais en utilisant l'héritage. Ainsi, vous pouvez créer votre propre contrôle MyComboBox: ComboBox. Créer un style pour elle - obtenir ComboBox style par défaut here

et écrire à la place (chercher ScrollViewer par nom):

< ScrollViewer x: Name = "ScrollViewer" BorderThickness = "0" Rembourrage = "1 ">

< ItemsPresenter /> 

</ScrollViewer>

ce

< ScrollViewer x: Name = "ScrollViewer" BorderThickness = "0" Rembourrage = "1">

< StackPanel x:Name="StackPanel" > 

    < ItemsPresenter /> 

</StackPanel> 

</ScrollViewer>

Ce StackPanel vous pouvez obtenir dans code:

classe publique MyComboBox: ComboBox {

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

}

L'héritage est plus puissant. Cela permet de travailler avec les éléments du template. Si vous avez décidé d'injecter ItemsPanel, vous devez comprendre que:

1) il est impossible d'utiliser le code avec référence de référence sur le panneau injecté.
2) pour se référer au panneau injecté, ce panneau doit être enregistré lui-même dans une certaine mémoire, par ex.

< ComboBox>

< ComboBox.ItemsPanel> 

     < ItemsPanelTemplate> 

      < StackPanel> 

      < i:Interaction.EventTriggers> 

       < i:EventTrigger EventName="Loaded"> 

        < RegisterMyInstanceInAccessibleFromCodePlaceAction/> 

       < /i:EventTrigger> 

      < /i:Interaction.EventTriggers> 

      < /StackPanel> 

     < /ItemsPanelTemplate> 

    < /ComboBox.ItemsPanel> 

</ComboBox>

Bonne chance!

+0

Merci pour la réponse. Je vais jouer avec ces suggestions demain et voir ce que je peux trouver. – RationalGeek

+1

Merci Vladimir. Vous obtenez les points et la réponse, parce que cela m'a conduit à une solution viable. Il s'avère cependant que vous n'avez pas besoin de vous soucier de l'insertion d'un StackPanel. Bien que l'événement KeyDown ne se déclenche pas lorsque ComboBox est ouvert, si vous sous-classez le contrôle, la méthode OnKeyDown * est * appelée. Etrange mais vrai. – RationalGeek

5

Cela devrait fonctionner. J'ai montré comment vous pouvez changer l'orientation ci-dessous. Vous pouvez ajouter des appels SetValue supplémentaires pour modifier d'autres propriétés.

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; 

Vous trouverez des informations plus détaillées dans cet article: http://www.codeproject.com/KB/WPF/codeVsXAML.aspx

+0

Cet article est pour implémentation dans WPF, Silverlight n'a pas FrameworkElementFactory. – AnthonyWJones

+0

Droite. Désolé pour ça. –

+0

J'ai trouvé la bonne solution pour un problème différent, merci. – jonas

4

Qu'est-ce que vous voulez vraiment construire est programme ceci: -

<ItemsPanelTemplate> 
    <StackPanel /> 
</ItemsPanelTemplate> 

Votre comportement sera alors assigner à la propriété ItemsPanel du ComboBox il est attaché à. Actuellement votre comportement est du code pur mais il n'y a aucun moyen de créer ce qui précède purement en code.

Comme c'est un petit morceau pour XAML la meilleure approche consiste à utiliser la XamlReader: -

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

Cela pourrait être sur la bonne voie pour moi. Je ne savais pas à propos de XamlReader. Cependant, je dois pouvoir programmer ce StackPanel, afin de m'abonner à quelques événements. Votre solution pourrait-elle être modifiée pour permettre cela? – RationalGeek

0

J'ai trouvé votre message en essayant de définir le ItemsPanel à partir du code afin que je puisse implémenter un VirtualizingStackPanel. Quand il y a des centaines d'éléments dans ma liste, la performance est nulle. Quoi qu'il en soit .. voici comment je l'ai fait.

1) contrôle personnalisé
2) Comportement personnalisée - vous pouvez également appliquer tout ce comportement à la normale ComboBox - soit à chaque fois, ou par un style.

- vous pouvez également exposer la valeur du délai d'attente pour pouvoir être outrepassée dans xaml.
- aussi, il semble que cela ne fonctionne pas lorsque la liste déroulante elle-même est ouverte. ne sais pas pourquoi exactement .. n'a jamais regardé en elle

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

} 
Questions connexes