2016-06-29 42 views
6

Sto tentando di utilizzare un SelectButton (https://gist.github.com/loraderon/580405) ma è necessario specificare MinWidth per esso. Altrimenti la larghezza è solo la larghezza di Extender. Rimuovere ColumnSpan o impostare la prima colonna Auto non sta facendo il trucco. Mi piacerebbe che avesse sempre la larghezza dell'elemento più largo nel simbolo list + extender.Come fare un "SelectButton" con Button, Extender e ListBox per avere la larghezza di cui ha bisogno?

<UserControl x:Class="loraderon.Controls.SelectButton" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:my="clr-namespace:loraderon.Controls" 
     mc:Ignorable="d" 
     SizeChanged="UserControl_SizeChanged" 
     d:DesignHeight="30" d:DesignWidth="100"> 
<Grid 
    x:Name="SplitGrid" 
    > 
    <Grid.ColumnDefinitions> 
     <ColumnDefinition Width="*" /> 
     <ColumnDefinition Width="23" /> 
    </Grid.ColumnDefinitions> 
    <Button 
     x:Name="Button" 
     Click="Button_Click" 
     Grid.ColumnSpan="2" 
     Padding="0" 
     HorizontalContentAlignment="Left" 
     > 
     <ContentControl 
      x:Name="ButtonContent" 
      HorizontalContentAlignment="Center" 
      ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemTemplate}" 
      /> 
    </Button> 
    <Expander 
     x:Name="Expander" 
     Expanded="Expander_Expanded" 
     Collapsed="Expander_Collapsed" 
     Grid.Column="1" 
     VerticalAlignment="Center" 
     IsExpanded="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=IsExpanded}" 
     /> 
    <Popup 
     IsOpen="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=IsExpanded}" 
     PlacementTarget="{Binding ElementName=Button}" 
     PopupAnimation="Fade" 
     StaysOpen="False" 
     > 
     <ListBox 
      x:Name="ListBox" 
      SelectionMode="Single" 
      SelectionChanged="ListBox_SelectionChanged" 
      SelectedIndex="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=SelectedIndex, Mode=TwoWay}" 
      ItemTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemTemplate}" 
      ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemsSource}" 
      /> 
    </Popup> 
</Grid> 
</UserControl 

EDIT: La finestra ho messo il controllo ha avuto:

SizeToContent="WidthAndHeight" 

che ha portato entrambe le risposte che seguono non al lavoro. C'è una soluzione più robusta che funzionerebbe quando si posiziona il pulsante in una varietà di controlli/contenitori? Sembra che il modo in cui è stato costruito il controllo non sia molto robusto. Popup non essendo la parte dell'albero visuale lo rende una cattiva scelta.

+0

Non è possibile impostare la larghezza su di esso quando lo si utilizza? kenny

+0

grazie per la risposta, ma ovviamente no. non posso sapere per quanto tempo gli oggetti sono in diverse lingue. più il controllo dovrebbe dimensionare correttamente il contenuto se la dimensione non è specificata. –

+0

Forse è possibile introdurre alcune proprietà di stiramento orizzontale sul controllo utente e sulla griglia. – kenny

risposta

2

Questo non è bello, ma funziona. Dato che hai già eseguito Code-Behind, questo potrebbe soddisfare le tue esigenze:

Innanzitutto, lo ItemsSourceProperty. Cambiarlo in:

public static readonly DependencyProperty ItemsSourceProperty = 
    DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(SelectButton), new PropertyMetadata(ItemsSourceChanged)); 

In secondo luogo, preparare Costruttore:

public SelectButton() { 
     InitializeComponent(); 
     this.ListBox.Loaded += this.ListBoxOnLoaded; 
    } 

In terzo luogo, attuare ItemnsSourceChanged -Metodo:

private static void ItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { 
     var self = d as SelectButton; 
     self.ListBoxOnLoaded(self.ListBox, new RoutedEventArgs()); 
    } 

In quarto luogo, fare la magia:

private void ListBoxOnLoaded(object sender, RoutedEventArgs routedEventArgs) { 
     var lb = sender as ListBox; 
     lb.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); 
     this.col1.MinWidth = lb.DesiredSize.Width; 

    } 

Ultimo ma non meno importante, edi t XAML:

<Grid x:Name="SplitGrid"> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="*" Name="col1" /> 
      <ColumnDefinition Width="23" /> 
     </Grid.ColumnDefinitions> 

Quando il listbox è stato caricato, facciamo semplicemente la misura da ourself ed applicare la dimensione desiderata alla prima colonna.

Speranza che aiuta :)

+0

grazie! lo guardo in poche ore! –

+0

non funziona. ListBoxOnLoaded risultati MinWidth per essere 4.0 –

+0

Lo so. Questo succede alla prima esecuzione, prima che ItemsSource sia vincolato. Dopo aver implementato e chiamato il metodo 'ItemsSourceChanged', la misurazione funzionerà;) – lokusking

3

La parte facile è vincolante per la ListBox' ActualWidth

<Grid.ColumnDefinitions> 
     <ColumnDefinition Width="{Binding ElementName=ListBox, Path=ActualWidth}"/> 
     <ColumnDefinition Width="23" /> 
    </Grid.ColumnDefinitions> 

La parte difficile è che, poiché il ListBox si trova in un'altra, con la sua struttura ad albero visuale (Remarks), viene visualizzato solo quando IsOpen è impostato su true.

La soluzione è una rapida apertura/chiusura quando il controllo viene caricato

public SelectButton() 
{ 
    InitializeComponent(); 
    Loaded += (o, e) => Initialize(); 
} 

void Initialize() 
{ 
    IsExpanded = true; 
    IsExpanded = false; 
} 

e un aggiornamento Metodo Expander_Expanded

private DateTime startUpTime = DateTime.Now; 
private DateTime collapsedAt = DateTime.MinValue; 

private void Expander_Expanded(object sender, RoutedEventArgs e) 
{ 
    if (DateTime.Now - startUpTime <= TimeSpan.FromMilliseconds(200)) 
    { 
     IsExpanded = true; 
     return; 
    } 
    if (DateTime.Now - collapsedAt <= TimeSpan.FromMilliseconds(200)) 
    { 
     Expander.IsExpanded = false; 
     IsExpanded = false; 
     return; 
    } 
    IsExpanded = true; 
} 

EDIT

scopre il periodo di 200ms può essere troppo piccolo dipende dal sistema utilizzato, ha aggiunto una soluzione più robusta

private bool startUp = true; 
private DateTime collapsedAt = DateTime.MinValue; 

private void Expander_Expanded(object sender, RoutedEventArgs e) 
{ 
    if (startUp) 
    { 
     IsExpanded = true; 
     startUp = false; 
     return; 
    } 
    if (DateTime.Now - collapsedAt <= TimeSpan.FromMilliseconds(200)) 
    { 
     Expander.IsExpanded = false; 
     IsExpanded = false; 
     return; 
    } 
    IsExpanded = true; 
} 
+0

grazie! lo guardo tra poche ore. l'apertura implica il lampeggio visibile nella GUI? –

+1

@matti Normalmente no. Ma dal momento che hai chiesto che si è verificato una volta, quando il mio computer stava esaurendo la memoria (ottenendo slowisch), che subito dopo la ricompilazione potrei vederlo accadere. – Funk

+0

non funziona. Expander_Expanded viene chiamato solo quando si espande manualmente. –

1

Questa è una risposta orribile ma potrebbe dare un'idea a qualcuno. Creo una Listbox invisibile nella stessa posizione in cui si trova il contenuto del pulsante e associamo Grid.Column = "0" MinWidth a It's ActualWidth.

In qualche modo questo è un po 'troppo largo. La larghezza del ListBox è troppo ampia per essere assegnata a Grid.Column = "0". Gli elementi nella lista popup sono molto più stretti. Max di questi dovrebbe essere la larghezza assegnata a Grid.Column = "0".

Ho anche provato ad avere un buttton lì e creato una dipendenza addizionale per il suo contenuto. Quello era il migliore (la dimensione era perfetta), ma poi dovresti conoscere preferibilmente tutti gli articoli e le loro dimensioni in lingue diverse o almeno un elemento. Questo è ovviamente un enorme svantaggio.

EDIT: Se questo stesso risultato può essere raggiunto con ContentControl/ContentPresenter in qualche modo per evitare 2 ListBox, questo sarebbe molto meglio.

EDIT2: questo non funziona. La larghezza è la larghezza del 1 ° elemento in modo che l'ordine o l'oggetto sia rilevante.

Ecco il codice XAML:

<UserControl x:Class="loraderon.Controls.SelectButton" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:my="clr-namespace:loraderon.Controls" 
     mc:Ignorable="d" 
     SizeChanged="UserControl_SizeChanged" 
     d:DesignHeight="30" d:DesignWidth="100"> 
    <Grid 
     x:Name="SplitGrid" 
    > 
    <Grid.ColumnDefinitions> 
     <ColumnDefinition Width="*" MinWidth="{Binding ActualWidth, ElementName=ContentListBox}"/> 
     <ColumnDefinition Width="23" /> 
    </Grid.ColumnDefinitions> 
    <Button 
     x:Name="Button" 
     Click="Button_Click" 
     Grid.ColumnSpan="2" 
     Padding="0" 
     HorizontalContentAlignment="Left" 
     > 
     <ContentControl 
      x:Name="ButtonContent" 
      HorizontalContentAlignment="Center" 
      ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemTemplate}" 
      /> 
    </Button> 
    <ListBox 
     Grid.Column="0" 
      x:Name="ContentListBox" 
      Visibility="Hidden" 
      MaxHeight="{Binding ActualHeight, ElementName=Button}" 
      HorizontalAlignment="Stretch" 
      ItemTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemTemplate}" 
      ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemsSource}"/> 
    <Expander 
     x:Name="Expander" 
     Expanded="Expander_Expanded" 
     Collapsed="Expander_Collapsed" 
     Grid.Column="1" 
     VerticalAlignment="Center" 
     IsExpanded="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=IsExpanded}" 
     /> 
    <Popup 
     IsOpen="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=IsExpanded}" 
     PlacementTarget="{Binding ElementName=Button}" 
     PopupAnimation="Fade" 
     StaysOpen="False" 
     > 
     <ListBox 
      x:Name="ListBox" 
      SelectionMode="Single" 
      SelectionChanged="ListBox_SelectionChanged" 
      SelectedIndex="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=SelectedIndex, Mode=TwoWay}" 
      ItemTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemTemplate}" 
      ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type my:SelectButton}}, Path=ItemsSource}" 
      /> 
    </Popup> 
    </Grid> 
</UserControl 
+0

se ListBox può essere definito solo una volta, ciò andrebbe bene. Potrebbe essere fatto come una risorsa? Ci ho provato, ma non è potuto succedere. Qualsiasi risposta che renda migliore questa soluzione (ad esempio evitando 2 ListBox: es) sarà accettata. –

1

È possibile creare una temporanea ListBox e misurarla per trovare la dimensione desiderata per l'elemento.

Il luogo più appropriato per calcolare la dimensione è quando la proprietà ItemsSource cambia. È possibile raggiungere questo obiettivo modificando la proprietà di dipendenza in quanto tale:

public static readonly DependencyProperty ItemsSourceProperty = 
     DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(SelectButton), new PropertyMetadata(ItemSourceChanged)); 

Nel metodo ItemSourceChanged è possibile creare una temporanea ListBox, fanno avere i tuoi articoli, e misurare:

private static void ItemSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
{ 
    ContentControl control = new ContentControl(); 
    ListBox listBox = new ListBox(); 
    control.Content = listBox; 

    IEnumerable enumerable = e.NewValue as IEnumerable; 
    SelectButton selectButton = d as SelectButton; 

    foreach (var item in enumerable) 
    { 
     listBox.Items.Add(item); 
    } 

    listBox.Measure(new Size(Double.MaxValue, Double.MaxValue)); 
    selectButton.Button.Width = listBox.DesiredSize.Width; 
} 

Qui la linea control.Content = listBox; è necessario. Se lo ListBox non è contenuto in un controllo, la dimensione desiderata restituisce sempre 0.

+0

grazie. questo potrebbe funzionare. Ci provo in poche ore. –

+0

non funziona. se il 2 ° oggetto è come "Oggetto molto molto molto lungo" il controllo non è abbastanza lungo. Ho provato anche ad impostare la larghezza se ButtonContent invece di Button e rimuovere UserControl_SizeChanged –

+0

Sei sicuro? Posso creare con successo un pulsante con la lunghezza dell'elemento più lungo. Posso inviarti un esempio funzionante. Come usi il controllo nella tua finestra? –