WP7 - Scrolling ListBox in external ScrollViewer - wpf

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

Related

Zero-height of ScrollViewer-templated item inside ItemsControl?

I have an ItemsControl with arbitrary items. Some of the items are wrapped inside a ScrollViewer. The code-behind for these scrollable items makes use of the ViewportWidth (almost equivalent to ActualWidth) and ViewportHeight (almost equivalent to ActualHeight) properties to arrange/size its visual children. This works as long as I don't put the item inside an ItemsControl. When the item appears in an ItemsControl the value of ViewportHeight equals 0 - effectively making my item invisible. Note that I want to arrange the items vertically, giving all items equal height! No fancy stuff, just a regular StackPanel.
The templates are applied automatically using DataType:
<MyControl.Resources>
<DataTemplate DataType="{x:Type MyScrollableItem}">
<MyControlWrappedInScrollViewer Text="{Binding Text}" />
</DataTemplate>
<DataTemplate DataType="{x:Type MyItem}">
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</MyControl.Resources>
<ItemsControl ItemsSource="{Binding MyCollection}" />
The structure of MyControlWrappedInScrollViewer looks something like this:
<UserControl>
<Grid>
<ScrollViewer CanContentScroll="True">
<Canvas />
</ScrollViewer>
</Grid>
</UserControl>
Why does my ScrollViewer get the height of 0? How can I tell my ItemsControl to size the item appropriately? E.g. One item yields a height of the ItemsControl height. Two items yield half of it, and so on.
This did the trick:
<Style TargetType="{x:Type ItemsControl}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Columns="1" IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>

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.

Templating ItemControl items without using ItemsSource

I am trying to build a custom Silverlight ItemsControl. I want the users of this control to add items using XAML. The items will be other UI elements. I would like to add a margin around all added items and therefore I want to add an ItemTemplate.
I am trying to do this using the ItemsControl.ItemTemplate, but that does not seem to be used when binding to elements in XAML, i.e using the ItemsControl.Items property.
However, if I use the ItemsControl.ItemsSource property, the ItemTemplate is used.
Is there anyway to use the ItemTemplate even though I am not assigning ItemsSource?
This is my code so far
<ItemsControl x:Class="MyControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<toolkit:WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="20" Background="Red">
<TextBlock Text="Test text"/>
<ContentPresenter Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ItemsPresenter x:Name="ItemsPresenter"/>
<Button Command="{Binding SearchCommand}"/>
</Grid>
</Border>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
And when I use my control
<MyControl>
<Button Content="Button"/>
<Button Content="Button"/>
</MyControl>
This got me a display of the items, with a wrap panel layout but no applied data template.
Then I found this post that mentioned two methods to override.
Son in my code-behind of the class I have now
protected override bool IsItemItsOwnContainerOverride(object item)
{
return false;
}
protected override void PrepareContainerForItemOverride(DependencyObject element,
object item)
{
base.PrepareContainerForItemOverride(element, item);
((ContentPresenter)element).ContentTemplate = ItemTemplate;
}
BUT - this gets me two items, with the style (I.E a red textblock) but no actual content. The buttons in the list are not added. It feels like I am doing something wrong - any pointers on what?
Thanks!
If all you want to do is add some margin then you can just set the ItemContainerStyle instead of specifying a template:
<ItemsControl>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.Margin" Value="10" /> <!-- or whatever margin you want -->
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
This will allow you to set any property of the container control (which in the case of an ItemsControl will be a ContentControl) through the style.

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

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

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

Resources