Silverlight 4: Listbox doesn't shrink when its items shrink - silverlight

from this question, I drilled down the problem to a listbox, that doesn't resize, when the Listbox-Items shrink. It resizes accordingly, when the size of the items grow, but it doesn't shrink, when the size of the items decrease.
The items can grow/shrink because the items containing textboxes, that resize with the input.
Jeremiah suggested to start a new question with more code to show, so here we go:
Our evil listbox is part of a UserControl, that contains a StackPanel with a Label (HorizontalAlignment=Center), the listbox (HA=Left) and a Button (HA=Right). The listbox-items are datalinked to an ObservableCollection
You will recognize beautiful BackgroundColors on the ListBox and the ListBoxItems. I used them to be able to tell wheter the Items or the Listbox itself doesn't shrink. I found out, that the Items shrink, but the Listbox doesn't.
Ok, here is the code of my UserControl:
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Left">
<StackPanel.Background>
<SolidColorBrush Color="{StaticResource ColorBasicDark}"/>
</StackPanel.Background>
<sdk:Label x:Name="LabelServiceName" FontSize="{StaticResource FontSizeMedium}" Margin="2" HorizontalAlignment="Center" Content="LabelServiceName">
<sdk:Label.Foreground>
<SolidColorBrush Color="{StaticResource ColorBasicLight}"/>
</sdk:Label.Foreground>
</sdk:Label>
<ListBox x:Name="ListBoxCharacteristics" BorderBrush="{x:Null}" Margin="0" HorizontalContentAlignment="Left" FontSize="9.333" HorizontalAlignment="Left">
<ListBox.Foreground>
<SolidColorBrush Color="{StaticResource ColorBasicLight}"/>
</ListBox.Foreground>
<!-- DataTemplate to display the content -->
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="StackPanelBorder" Orientation="Horizontal" HorizontalAlignment="Left">
<TextBox x:Name="TextBoxCharacteristicName" Style="{StaticResource InputTextBox}" Text="{Binding Name}" />
<TextBox x:Name="TextBoxSep" Style="{StaticResource ReadOnlyTextBox}" Text="=" />
<TextBox x:Name="TextBoxFuncOrValue" Style="{StaticResource InputTextBox}" Text="{Binding Value.Text}" />
<TextBox x:Name="TextBoxValue" Style="{StaticResource ReadOnlyTextBox}" />
<Button x:Name="ButtonRemove" Style="{StaticResource BasicButtonStyle}" Content="-" Click="ButtonRemove_Click" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Background" Value="Yellow" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.Background>
<SolidColorBrush Color="Red" />
</ListBox.Background>
</ListBox>
<Button x:Name="ButtonAddCharaDisplayObject" Style="{StaticResource BasicButtonStyle}" Content="+" HorizontalAlignment="Right" Click="ButtonAddCharaDisplayObject_Click" />
</StackPanel>
I have no idea why the listbox doesn't shrink when the size of the items shrink, although I have set the listbox' size to Auto and HorizontalAlignment to Left
Thanks in advance,
Frank

I finally found the solution in this post. The problem is, that from Silverlight 3 on, the ListBox uses VirtualizationStackPanel to display the ListItems. Other than StackPanel, VirtualizationStackPanel uses all the space it gets and never gives it back. So, when the biggest item in your list shrinks and therefor the ListBox itself could shrink because now there is unused space, the ListBox' width (and height for that matter) will still stay the same because of VirtualizationStackPanel doesn't shrink properly.
To fix this, we can force the ListBox to use StackPanel instead of VirtualizationStackPanel. Note, that this may come at the cost of performance!
<ListBox HorizontalContentAlignment="Left" FontSize="9.333" HorizontalAlignment="Left">
... // other listbox related stuff
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>

Well... I don't have all of your code. But, I simplified what you had above to this and it works.
I hope this will help you, in some way, figure out your problem. Once again, it could be the parent of this control causing the problems. It could also be one of your styles you are applying. Try stripping out EVERYTHING from your control that doesn't have to be there, then add it back slowly to find the culprit.
I created a new silverlight application, and this is literally the only thing in it. The listbox grows and shrinks as expected.
XAML:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="Test.MainPage">
<Grid x:Name="LayoutRoot">
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Left">
<StackPanel.Background>
<SolidColorBrush Color="Black"/>
</StackPanel.Background>
<ListBox x:Name="ListBox" BorderBrush="{x:Null}" Margin="0" HorizontalContentAlignment="Left" FontSize="9.333" HorizontalAlignment="Left">
<ListBox.Foreground>
<SolidColorBrush Color="Silver"/>
</ListBox.Foreground>
<!-- DataTemplate to display the content -->
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanelOrientation="Horizontal" HorizontalAlignment="Left">
<TextBox FontSize="30" Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Background" Value="Yellow" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.Background>
<SolidColorBrush Color="Red" />
</ListBox.Background>
</ListBox>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Height="30">
<Button Content="Add" Click="Add_Click" Width="100"/>
<Button Content="Remove" Click="Remove_Click" Width="100"/>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
Code Behind:
using System;
using System.Windows;
using System.Windows.Controls;
namespace Test
{
public partial class MainPage : UserControl
{
public MainPage()
{
// Required to initialize variables
InitializeComponent();
Count = 8;
}
private int Count;
private void Add_Click(object sender, System.Windows.RoutedEventArgs e)
{
Count = Count * 8;
ListBox.Items.Add("Hi Mom (" + Count.ToString() + ")");
}
private void Remove_Click(object sender, System.Windows.RoutedEventArgs e)
{
ListBox.Items.RemoveAt(ListBox.Items.Count-1);
}
}
}

Related

How To Make A WPF UserControl Act As A Container

I'm trying to create a Toolbar control that can group selected buttons with a border and a label. If there is already a built-in control that will do this then I could use that instead of building a UserControl.
If not, then what I'm wanting to build is a UserControl that would allow me to enter one-to-many of my ImageButton UserControls and set a GroupLabel text like below. Can this be done in WPF?
<User_Controls:ToolbarGroup GroupLabel="Entity">
<User_Controls:ImageButton ButtonText="Entity Setup"/>
<User_Controls:ImageButton ButtonText="New Entity"/>
</User_Controls:ToolbarGroup>
PS: I would post an image but this quirky forum won't allow me to post an image.
If i have got you correctly then I think you can achieve this way also, and on mouse eneter and leave event you can do the button click job.
for setting text you can use a grid and a label inside it to set the text, and Image buttons below it.
<UserControl x:Class="ABC.View.Oats"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Image Source="{Binding Image}" Stretch="Fill"/>
</Grid>
</UserControl>
I think what you're looking for is a GroupBox, it has a header property where you can set the label.
Something like this:
<GroupBox Width="300" Height="100">
<GroupBox.Header>
<Label>Text</Label>
</GroupBox.Header>
<StackPanel>
<Button Content="Button"/>
<Button Content="Button"/>
<Button Content="Button"/>
</StackPanel>
</GroupBox>
I would also recommend using the groupbox, it seems to be doing exactly what you want it to do and it looks neat. Here's some examples on how to use them: http://www.dotnetperls.com/groupbox-wpf
On the other hand, if you believe the groupbox is not sufficient, you could create a control that inherits from the groupbox and you could extend it and add whatever you need to it. It would look like this:
public class customGroupBox: GroupBox{
....Add whatever you need here
}
Thanks for the replies. I tried the GroupBox and it's not the layout we want because we want the label underneath the buttons and centered. I never could find a way to add a collection to the UserControl. Maybe I didn't ask the question right by calling it a container. The code below will work, but it's not elegant. I wanted something that would wrap the layout in a UserControl and allow me to add a variable number of buttons to each toolbar group.
<StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0">
<Border Background="GhostWhite" BorderBrush="Gainsboro" BorderThickness="1">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<User_Controls:ImageButton ButtonText="New 1"/>
<User_Controls:ImageButton ButtonText="New 2"/>
<User_Controls:ImageButton ButtonText="New 3"/>
</StackPanel>
<Label HorizontalAlignment="Center" Content="Group 1"/>
</StackPanel>
</Border>
<Border Background="GhostWhite" BorderBrush="Gainsboro" BorderThickness="1">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<User_Controls:ImageButton ButtonText="New 4"/>
</StackPanel>
<Label HorizontalAlignment="Center" Content="Group 2"/>
</StackPanel>
</Border>
</StackPanel>
One way to accomplish this is with a custom styled ItemsControl.
You can then reuse it and just bind it to different data.
Please forgive me, this is hand-typed...
In your resources...
<Style x:Key="ToolbarGroupItemsControlStyle" TargetType="ItemsControl">
...
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<Grid>
... XAML to form your group with a binding to the
... group name
<ItemsPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="ToolbarGroupItemTemplate">
<Grid>
... XAML and binding for each toolbar group item ...
</Grid>
</DataTemplate>
In your XAML...
<ItemsControl
Style="{DynamicResource ToolbarGroupItemsControlStyle}"
ItemsSource="{Binding ToolbarGroupItems}"
ItemTemplate="{DynamicResource ToolbarGroupItemTemplate"/>
If your resources above are at the application level, then you can place the ItemsControl above on any Window/UserControl you want.
Your ItemsSource will need to be a collection of a custom type you create that has bindings for the button text, etc.
I hope this is helpful.

wpf grid as itemspaneltemplate gives inconsistent heights

im trying to make a week scheduler
so for each day i have a listbox with a grid as its itemspaneltemplate , and each individual session gets its row and rowspan from the database and its working very well
the problem is when i have a short appointment so the rowspan is only 2 or 3 and the content is longer, in that case the rows containing the appointment grow to accommodate the larger content
i don't want that, id rather the appointments reflect their time span even if i don't see all data
i tried many combinations of answers that i found online
heres the whole markup
<ListBox Name="lsbTasks" Loaded="lsbTasks_Loaded_1" HorizontalContentAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Disabled" VerticalContentAlignment="Stretch" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Grid Initialized="Grid_Initialized_1" Background="Thistle" IsSharedSizeScope="True"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding BusyFrom,Converter={StaticResource BusyFromConverter}}" />
<Setter Property="Grid.RowSpan" Value="{Binding BusyMinutes,Converter={StaticResource BusyMinutesConverter}}" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
</Style>
</ItemsControl.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<ScrollViewer ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.IsDeferredScrollingEnabled="True">
<StackPanel Background="Purple" PreviewMouseDown="DockPanel_PreviewMouseDown_1">
<TextBlock TextAlignment="Center" TextWrapping="Wrap" Text="{Binding SessionPlace}" Foreground="Thistle" DockPanel.Dock="Bottom" />
<TextBlock TextAlignment="Center" TextWrapping="Wrap" Text="{Binding ClientName}" Foreground="White" FontWeight="Bold" DockPanel.Dock="Top" />
<TextBlock TextAlignment="Center" TextWrapping="Wrap" Text="{Binding OppositionName}" Foreground="White" DockPanel.Dock="Top" />
<TextBlock TextAlignment="Center" TextWrapping="Wrap" Text="{Binding SubjectName}" Foreground="Thistle" />
</StackPanel>
</ScrollViewer>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and heres the initialization code
Private Sub Grid_Initialized_1(sender As Grid, e As EventArgs)
Dim mnts = MyWorkDaySpan.TotalMinutes
For i = 1 To mnts / 10
Dim rd = New RowDefinition With {.Height = New GridLength(1, GridUnitType.Star)}
'rd.SharedSizeGroup = "RowGroup"
sender.RowDefinitions.Add(rd)
Next
End Sub
but still the rows are uneven, as proven by the gridlines
id appreciate any help or guidance
You should use a different Panel, perhaps a Canvas and bind the Canvas.Top and Height properties in the ListBoxItem style.
Another alternative would be to write a custom Panel class ("CalendarPanel"), which defines two attached properties BusyFrom and BusyMinutes and arranges its child elements according to the values of these properties.

ListView for timetable

I'm developing an app that presents a timetable, that looks like this:
However, I have several problems with it:
When there are too many items (minutes) to display in 1 row it
should be broken automatically in several rows (e.g. all rows after
06 should be wrapped)
I don't know where the spacing around minute items comes from. It's not
item`s margin.
When scrolling the timetable list with finger, it only gets scrolled if no minute box is touched. Otherwise the minute box moves a bit, not the
entire timetable list.
The timetable list is bound to an ObservableCollection of TimetableHour instaces:
public class TimetableHour
{
public sbyte Hour { get; set; }
public IList<TimetableItem> Items { get; set; }
public string HourString
{
get { return Hour.ToString("00") + ":"; }
}
}
and the XAML page:
<Style TargetType="ListView" x:Key="TimetableListViewStyle">
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden" />
<Setter Property="Margin" Value="0,0,60,0" />
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding HourString}"
Width="60" Height="50"
TextAlignment="Center"
Background="CornflowerBlue" Foreground="White" BorderThickness="0"
FontSize="23"
Padding="10">
</TextBox>
<GridView ItemsSource="{Binding Items}" Height="Auto" SelectionMode="None" IsTapEnabled="False" IsHoldingEnabled="False" >
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid Orientation="Horizontal" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Background="LightSkyBlue" Width="60" Height="50">
<TextBlock Text="{Binding Minute}" HorizontalAlignment="Center" FontSize="17" FontWeight="Medium" Margin="0,2,0,0"></TextBlock>
<TextBlock HorizontalAlignment="Center">Tip</TextBlock>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<ListView Grid.Row="2" Grid.Column="2" ItemsSource="{Binding Timetable}"
Style="{StaticResource TimetableListViewStyle}"
SelectionMode="None" />
Ok, so I figured them out:
To make the minutes items wrap I needed to specify the exact width
of the GridView that is used to display them. Alternatively I can use a Grid instead of a StackPanel, to allow to use all available space - see GridView width inherited from parent
To control the spacing around GridView items you need to define the
ItemContainerStyle as described here:
Windows8 ListView and space between items
To achieve this I can set IsSwipeEnabled="False" on the GridView.

How do I access elements that have been dynamically assigned to a control in the form of an XAML resource?

I have the following resource in my window that declares how a certain kind of TabItem should look like.
<Window.Resources>
<StackPanel x:Key="TabSearchContents" x:Shared="False"
Orientation="Vertical">
<Border
BorderThickness="3"
BorderBrush="Purple">
<TextBlock
Text="SEARCH BOOKS"
FontFamily="Verdana"
FontSize="25"
Foreground="Blue"
HorizontalAlignment="Center" />
</Border>
<StackPanel
Height="30"
Orientation="Horizontal"
Margin="5">
<TextBox
x:Name="txtSearch"
Width="650"
FontFamily="Comic Sans MS"
Foreground="Chocolate" />
<Button
x:Name="btnSearch"
Width="100"
Content="Go!"
Click="BtnSearch_Click" />
</StackPanel>
<Grid x:Name="gridResults">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="450"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="itmsSearch" ItemsSource="{Binding}" Padding="4"
ItemTemplate="{StaticResource SearchResultItemDT}">
</ItemsControl>
</ScrollViewer>
<StackPanel x:Name="stkpnlDetails">
</StackPanel>
</Grid>
</StackPanel>
</Window.Resources>
Then, in my code-behind, I dynamically create a tab and assign to the TabControl that is already present in my window.
void BtnNewTab_Click(object sender, RoutedEventArgs e)
{
TabItem tb = new TabItem();
tb.Content = this.Resources["TabSearchContents"];
tb.DataContext = _bridge.SearchBooksByTitle("e");
tb.Header = "Wuttp yo!";
Button btnGo = ((Button)tb.FindName("btnSearch"));
ItemsControl i = (ItemsControl)tb.FindName("itmsSearch");
btnGo.Resources.Add("ResultList", i);
daTabs.Items.Add(tb);
tb.Focus();
}
I want to access the btnSearch Button that is declared in my XAML resource.
As it is, this code throws an exception since btnGo turns out to be null (as well as i) since it can't find the expected control via FindName().
I read about the RegisterName() method, but it requires a reference to an instance of the required control... which I don't have.
I dont think you should define your button like this, try defining it in a style, creating a button and assigning the button that style, i think you will be able to get what you are going for this way.
myTheme.xaml
<ResourceDictionary
<Style x:Key="btnSearch" TargetType="{x:Type Button}">
<Setter Property="Width" Value="100"/>
<Setter Property="Content" Value="Go!"/>
<Setter Property="Click" Value="btn_Click"/>
</Style>
ResourceDictionary/>
myCode.cs
Button btnGo = new Button;
btnGo.Style = "{DynamicResource btnSearch}";
Hope this helps,
Eamonn

WP7 - Scrolling ListBox in external ScrollViewer

I have the following page layout in my application:
<Grid x:Name="ContentPanel"
Grid.Row="1">
<ScrollViewer x:Name="ScrollViewer1"
MaxHeight="600"
VerticalAlignment="Top"
HorizontalAlignment="Stretch">
<StackPanel x:Name="StackPanel1" >
<TextBlock x:Name="TextBlock1" />
<toolkit:ListPicker x:Name="ListPicker1" />
<TextBlock x:Name="TextBlock2" />
<TextBox x:Name="TextBlock3" />
<TextBlock x:Name="TextBlock4" />
<StackPanel x:Name="StackPanel2" >
<TextBlock x:Name="TextBlock5" />
<Image x:Name="Image1"/>
</StackPanel>
<ListBox x:Name="ListBox1">
<!--Customize the ListBox template to remove the built-in ScrollViewer-->
<ListBox.Template>
<ControlTemplate>
<ItemsPresenter />
</ControlTemplate>
</ListBox.Template>
<ListBox.ItemTemplate>
<DataTemplate>
<!-- .... -->
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</StackPanel>
</ScrollViewer>
</Grid>
I added an external ScrollViewer instead of the using the ListBox's because without it the stuff above the ListBox was taking too much room and not leaving enough space to view the ListBox contents.
Now, the problem is if I add an item to the end of the ListBox the ScrollIntoView method does not work. So I need to use the ScrollViewer's ScrollToVerticalOffset method.
I'm adding the new item to an ObservableCollection that is bound to the ListBox when the user clicks a button on the application bar. How can I calculate the value to be passed to ScrollViewer.ScrollToVerticalOffset?
Thanks for your help!
You can find the container that the ListBox has generated to host your element. Once you have this container, you can find its position relative to the scrollviewer:
var newItem = // the item you just added to your listbox
// find the ListBox container
listBox.UpdateLayout()
var element = listBox.ItemContainerGenerator.ContainerFromItem(newItem) as FrameworkElement;
// find its position in the scroll viewer
var transform = element.TransformToVisual(ScrollViewer);
var elementLocation = transform.Transform(new Point(0, 0));
double newVerticalOffset = elementLocation.Y + ScrollViewer.VerticalOffset;
// scroll into view
ScrollViewer.ScrollToVerticalOffset(newVerticalOffset);
Hope that helps

Resources