VirtualizingStackPanel and TextWrapping bug? Windows Phone - c#

I have a strange behaviour with VirtualizingStackPanel. I have a list with items that contains TextBlock with TextWrap="Wrap". Here is the code:
<ListBox x:Name="messagesList" ItemsSource="{Binding Messages}" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
...
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<CheckBox Style="{Binding Own, Converter={StaticResource MsgTypeToStyle}}"
Tag="{Binding TimeString}"
IsEnabled="True">
<TextBlock Text="{Binding Content}" TextWrapping="Wrap"/>
</CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It works pretty good, but if I try to scroll very fast (using mouse on emulator, not prommatically) there is some lagging in scroll, probably HorizontallOffset sometimes calculates wrong, and in the bottom in ends with very strange result (see image, right image demostrates normal behaviour).
After research I figured out that problem in combination VirtualizingStackPanel and TextBlock.TextWrap="Wrap", if I remove one element from this couple all works correctly.
But I need virtualization because of big items count, and TextWrap for correct text displaying.
So I think about making my own implementation of Virtualizing Panel, can you please guide me, how to do this, or how to fix current problem?
UPD: The problem:
on the first two images ListBox is already(!) scrolled to the bottom (it can't be scrolled down any more), but elements placed incorrectly, correct placing shown on the right image. This happens only if you will scroll very fast.
UPD2: Thanks to Milan Aggarwal. He provided a good case of my problem here. Seems that is really a bug in ListBox. Workaround, provided doesn't fit in my scenario, because I need to interact with controls inside ListBox item.
Now I'm trying to catch ManipulationCompleted event and check if it is Inertial, if so that means scroll and I set focus to page:
void messagesList_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
{
if (e.IsInertial)
this.Focus();
}
P.S. thanks for good luck wishes;)

Inorder to overcome the black occurrence on scrolling you need to virtualize your scroll control. For that you should inherit IList and create a Collection of your own similar to ObservableCollection in which you will have to override the default indexer depending on your caching requirement and simultaneously maintain a cache for your items. I feel this might be what you are looking for: http://blogs.msdn.com/b/ptorr/archive/2010/08/16/virtualizing-data-in-windows-phone-7-silverlight-applications.aspx
There is a sample project on that page. Try that out.
I also feel that you are facing this problem http://blog.rsuter.com/?p=258. I guess this will be solved using virtualization itself. Hope it helps

What is the problem on 2nd screen? You mean large empty space after last message? Last message not being put at the bottom of page? Am I getting this right?
Actually i didn't try to reproduce your code and bug but my point is that since you are using 3rd party libs (Silverlight Toolkit) in your app, why not to use Coding4Fun Tools as well?
I wrote this dummy class:
public class Message
{
public string Time { get; private set; }
public string Content { get; private set; }
public Message(string time, string content)
{
Time = time;
Content = content;
}
}
Then i put this dummy code in main page constructor:
var _messages = new ObservableCollection<Message>();
for (int i = 0; i < 50; i++)
{
_messages.Add(new Message("12:40", "Teh very long string which should be wrapped. Pavel Durov gives WP7 Contest winners less money than he did for Android/iOS winners. FFFUUUUUUUUUUUUUU "));
}
this.ListBox.ItemsSource = _messages;
And in xaml i put a listbox with chat bubble control of toolkit:
<ListBox x:Name="ListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<c4fToolkit:ChatBubble>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{Binding Content}"
TextWrapping="Wrap"/>
<TextBlock Grid.Row="1"
Text="{Binding Time}"
HorizontalAlignment="Right"/>
</Grid>
</c4fToolkit:ChatBubble>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I ran it and saw no strange behaviours, maybe little bit of lagging.
There is the result:
Hope i did some help.
Удачи с конкурсом, я б сам поучаствовал, но не верю, что за 1,5 месяца удастся сделать приличный клиент.

What is the problem at the second image? Is it that the BlackArea showed up at all, or is it that the ScrollView did not "bounceback" after the scrolling ended to fill the black area with content?
It is crucial to know this, because ... this effect is simply natural to WPF/WP7..
BTW. the scroller should "bounceback" after the over-scrolling, but it seems it has a bug and sometimes forgets to do it.
I'm asking because black/white areas are quite often not only on WP7, but and anywhere where there's a XAMLish ScrollViewer with inertial scrolling. You see, when scrolling faster than the platform can provide new items, you just scroll outside of the pre-calculated items and once you "over"scroll very far, the slowly-incoming items abruptly discover that there are no more items, hence - back empty area..
This happens mostly when some conditions happen at once: you scroll fast (yay), your bindings are slow (overly complicated expressions), your template-rendering is slow (complex non-reusable UI templates), the bindings does not know how many items there are (bound to IEnumerable not IList -> no .Count information!), or the renderer doesnt know how much height to reserve for an item (the item is not constant-height, but eveyr item has to be calculated to determine its own exact height).
If you want to correct it, you must fight with those causes first or you must implement your own Scrolling or hack into virtualizing stack panel and synchronize the feeding of items from bindingsource with the scrolling to let the scroller guess the total height better: for example, display only a small items at once, listen to scrolling events and add/remove next pack of items from head/tail of the list dynamically.. This will solve the problem by not allowing to scroll fast :)))))
I believe that constraining the item height is the easiest way. Can't you do something smart to make the items actually constant-sized?

I suggest you create the simplest possible function that computes a height for the wrapped textblock and bind to that. This way, you get the benefit of variable height items in the listbox, but get to keep using the virtualizing stack panel.

Related

Listbox with thousand of images in a wrappanel

I'm trying to create a listbox that may contain over a thousand images in a grid like design. In term of design it would be quite similar to this:
Since I can't use a wrappanel as that would break UI virtualization and a stackpanel can't list images in such a grid(?), I'm trying to solve this issue using a modified version of https://virtualwrappanel.codeplex.com
My XAML:
<ListBox x:Name="GameWheel" ItemsSource="{Binding GameData}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<c:VirtualizingWrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image x:Name="GameImage" Source="{Binding Path=ImagePath}" Width="{Binding ElementName=GameWheel, Path=ActualWidth, Converter={StaticResource widthConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
While this approach works, it's still quite slow and buggy, especially when using bind on the image width. Is there a better way of archiving the same result? Without the custom wrappanel preferably.
Check out my implementation of VirtualizingWrapPanel. Also available (with more included) from NuGet.
This code originally came from this CodeProject article which mostly worked and appears to be the same place you started. Along the way I fixed several bugs and improved performance so it might help you as well.
Update: The key is going to be to bind to the VirtualizingWrapPanel.ItemWidth rather than Image.Width. There was a bug in both implementations of the VirtualizingWrapPanel that would prevent the children from being resized if the ItemHeight or ItemWidth values changed. I fixed this bug in this commit (line 358).
Admittedly it's a brute force fix but I wanted to get something checked in for you and have to head out. I tested it with 10,000 images and it was very snappy. I'll work on a better solution (ie: only remeasure when we know the value has changed) and update this answer when I do.
Update 2: Quick change to improve the child.Measure with this commit.
//TODO: If either child Height or Width == PositiveInfinity and the other side == ChildSlotSize then we probably don't need to measure. Need to test this
if (!child.DesiredSize.Equals(ChildSlotSize))
{
child.Measure(ChildSlotSize);
}
Still room for improvement but no time for proper testing right now and this works.

Notebook-like background for a TextBox in a Windows Phone App

I am trying to create an application to take notes for windows phone 8.1
I want to give the user,a notebook type of feel.
For this I have created the UI for notes, the XAML is:
<Grid Margin="0,12.333,0,-0.333">
<Grid.Background>
<ImageBrush ImageSource="Images/notebookpaper.jpg"/>
</Grid.Background>
<TextBox TextWrapping="Wrap" Background="{x:Null}" Text="" BorderBrush="{x:Null}" HorizontalAlignment="Left" Margin="60,96,0,0" VerticalAlignment="Top" Height="480" Width="340" BorderThickness="0" GotFocus="TextBox_GotFocus" LostFocus="TextBox_LostFocus" FontFamily="Arial" TextChanged="TextBox_TextChanged" AcceptsReturn="True" FontSize="24.8"/>
<TextBlock Text="Date : " HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Top" Margin="246,10,0,0" Height="20" Width="59"/>
</Grid>
The image notebookpaper.jpg looks like this:
When user types in the text in text box, it looks like:
The problem is that, some characters appear a little above the line, some exactly on the line etc. which looks odd. Also, when I try to scroll, UI appears as:
The text appears striked out, as only the text scrolls and not the background image.
Also I want to be able to provide user a list of 5-6 fonts out of which they can select which one to use for typing the notes.
What should I do, so that the text appears properly aligned and text scrolls properly.
Is there any other way to do this ?
It looks like you have two problems:
Varying line height
Scrolling doesn't match the lines
To solve the first problem, you can probably work with TextBlock.TextLineBounds, talked about a bit in this MSDN blog post and the TextLineBounds enumeration documentation. This only seems to apply to TextBlocks, so you might have to swap between a TextBlock and TextBox as users edit their text.
To solve the second problem, the TextBox styles and templates page has a lot of helpful info. It looks like you can make your ImageBrush the background of your control by overriding TextBoxButtonBackgroundThemeBrush. If that doesn't work when focused, you may have to take the entire template given on the linked page and edit it to put your image in the background (there's a lot of XAML, but you should just be able to put your image in BackgroundElement or just before it).
If it still doesn't scroll, you can try setting ScrollViewer.Background instead; if that doesn't work, you'll need to handle the ScrollViewer.ViewChanging or ScrollViewer.ViewChanged events (probably by overriding it) so that it you can transform the background image by the amount of pixels the scrollviewer has moved.
You can also find the ScrollViewer in your code-behind (and skip dealing with the template) by using VisualTreeHelper. This would allow you to set the background of the ScrollViewer and/or subscribe to its events. This however is more brittle than the other methods and is usually a last resort.

Get Listboxitem from listbox

Hi this should be faily simple, however I don't know what I am doing wrong. I've been looking all over the internet seeing people make this work, even followed the tutorial on MSDN still nothing has worked for me.
I want to Iterate over a ListBox, and get the ListBoxItems so I can find the DataTemplate that I have added to it.
This is my code behind.
private void SetListBoxDataTemplate(ListBox MyListBox)
{
try
{
foreach (CustomDataTemplateObject dataobject in MyListBox.Items)
{
ListBoxItem lbi = (ListBoxItem)(MyListBox.ItemContainerGenerator.ContainerFromItem(dataobject));
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(lbi);
DataTemplate dt = myContentPresenter.ContentTemplate;
TextBlock tb = (TextBlock)dt.FindName("ListBoxItemTextBlock1", myContentPresenter);
ComboBox cb = (ComboBox)dt.FindName("ListBoxItemComboBox1", myContentPresenter);
tb.Text = dataobject.Text;
cb.ItemsSource = dataobject.ListColors;
}
}
catch (Exception ex)
{
MessageBox.Show(""+ex);
}
}
XAML looks like this:
<DataTemplate x:Key="ListBoxItemDataTemplate1">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="1 1 0 1" MinWidth="50">
<TextBlock Name="ListBoxItemTextBlock1" Background="{Binding ElementName=ListBoxItemComboBox1, Path=SelectedValue}" >
</TextBlock>
</Border>
<ComboBox Name="ListBoxItemComboBox1" />
</StackPanel>
</DataTemplate>*
<StackPanel>
<ListBox Name="ListBoxTest1" ItemTemplate="{DynamicResource ListBoxItemDataTemplate1}" />
</StackPanel>
I have tried with setting my itemtemplate to static to see if it works, and the method i'm calling from code behind, is called after I have populated my ListBoxs
My dataobject is NOT null, however when i call the line in my code behind, my lbi, ends up being null.
Any suggestions? thanks in advance!
FIRST UPDATE
This problem only occurs if i call the method in my constructor, so perhaps it's because it hasn't initialized the full group element section yet. However I want to do this as soon as possible. Am I perhaps forced to do it in a WindowLoaded event?
SECOND UPDATE
Code updated, Rachel's answer worked for iterating over my ListBoxItems, however the Listbox Has not fully rendered since i'm unable to reach the Datatemplate at this time. So MyListBox_GeneratorStatusChanged is not working for this problem, but it does get the ListBoxItems.
WPF's main thread runs items at different priority levels. Code that runs in the Constructor all gets run at Normal priority, while things like rendering the ListBox and it's items run at the Render priority level, which occurs after all Normal priority operations have finished.
This means that your entire Constructor gets run (including SetListBoxDataTemplate()) before your ListBox is even rendered and the items get generated.
If you want to run some code after the items are generated, use the ItemsContainerGenerator.StatusChanged event
// Constructor
MyListBox.ItemContainerGenerator.StatusChanged += MyListBox_GeneratorStatusChanged;
...
void MyListBox_GeneratorStatusChanged(object sender, EventArgs e)
{
// return if containers have not been generated yet
if (MyListBox.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
return;
// remove event
MyListBox.ItemContainerGenerator.StatusChanged -= MyListBox_GeneratorStatusChanged;
// your items are now generated
SetListBoxDataTemplate(MyListBox);
}
What are you trying to accomplish with this method anyways? It is a bit unusual for WPF, and there may be a much better WPF way of accomplishing your task.
Updated based on new code added to Question
A much better method of setting your Text and ItemsSource properties is to make use of WPF's data bindings.
Your DataTemplate should look like this:
<DataTemplate x:Key="ListBoxItemDataTemplate1">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="1 1 0 1" MinWidth="50">
<TextBlock Text="{Binding Text}" Background="{Binding ElementName=ListBoxItemComboBox1, Path=SelectedValue}" >
</TextBlock>
</Border>
<ComboBox ItemsSource="{Binding ListColors}" />
</StackPanel>
</DataTemplate>*
A DataTemplate is like a cookie cutter. It's used to make the UI objects, but is not part of the UI object itself. All it does is tell WPF that "When you go to render this object, render it using this XAML". So the way your XAML gets rendered is
<ListBoxItem>
<StackPanel>
<Border>
<TextBlock Text="{Binding Text}" />
</Border>
<ComboBox ItemsSource="{Binding ListColors}">
</StackPanel>
</ListBoxItem>
In addition, the DataContext behind your ListBoxItem is the item from the collection bound to ListBox.ItemsSource, which based on your code should be CustomDataTemplateObject. That allows the bindings from the DataTemplate to work
If you're new to WPF and struggling to understand how exact the DataContext works, I'd recommend reading this article of mine: What is this "DataContext" you speak of?.
To summarize, WPF has two layers to an application: the UI layer and the Data Layer (DataContext). When you perform a basic binding like above, you are pulling data from the data layer into the UI layer.
So your ListBoxItem has a data layer of CustomDataTemplateObject, and the TextBlock.Text and ComboBox.ItemsSource bindings are pulling data from the data layer for use in the UI layer.
I'd also highly recommend using a utility like Snoop which lets you view the entire Visual Tree of a running WPF application to see how items get rendered. Its very useful for debugging or learning more about how WPF works.
You're confusing two jobs and mixing them into one. First, get access to the ListBoxItem:
private void SetListBoxDataTemplate(ListBox MyListBox)
{
foreach (ListBoxItem listBoxItem in MyListBox.Items)
{
}
}
Now you can get the DataTemplate from the ListBoxItem:
foreach (ListBoxItem listBoxItem in MyListBox.Items)
{
ContentPresenter presenter = FindVisualChild<ContentPresenter>(listBoxItem);
DataTemplate dataTemplate = presenter.ContentTemplate;
if (dataTemplate != null)
{
// Do something with dataTemplate here
}
}
The FindVisualChild method can be found in the How to: Find DataTemplate-Generated Elements page on MSDN.
UPDATE >>>
To answer your edit, yes, the constructor will be too early to try to access these DataTemplates because the Framework won't have applied them to all of the objects by then. It is best to use the FrameworkElement.Loaded Event to do these kinds of things, as that is the first event that can be called after the controls have all been initialised.

Get a textblock value in a listbox using GestureServices

Sorry guys, I had asked this question earlier but could not figure out the answer. Made an edit to see if that bumps it, but that did not seem to work. So here is the last try to the question
I can't seem to figure out how one can get the value of a specific textblock in a listbox. To start things off, here is the code:
<ListBox HorizontalAlignment="Left" Name="listItems" VerticalAlignment="Top" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="210" >
<Grid Height="210" Background="#75FFF8DC">
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener Tap="GestureListener_Tap"
DoubleTap="GestureListener_DoubleTap"
Hold="GestureListener_Hold"
Flick="GestureListener_Flick"/>
</toolkit:GestureService.GestureListener>
...CODE...
</></></>...
The code area contains a bunch of other grids, partitions (columns and rows) and textblocks. Here is an example:
<Image Name="XXX" Source="{Binding XXXPath}" Stretch="Fill"
Grid.Column="0"/>
<TextBlock Name="YYY" Grid.Column="1" Grid.Row="0"
Text="{Binding YYYPath}" Foreground="Black"/>
<TextBlock Name="ZZZ" Grid.Column="2" Grid.Row="0"
Text="{Binding ZZZPath}" Foreground="Black"/>
So what I want, is if someone taps the grid (that means anything in the grid, including these textblocks and images), I want to first get the text of the textblock "YYY."
I could have inserted that code into a textblock and used sender as textblock, but I do not want to limit my gestures to one textblock, nor do I want to repeat that for each element in the grid (lots of issues and seems unnecessary).
Edit: If this does not work, I can also implement just one tap gesture (but again, for the whole grid) and use that to get the value of the textblock. Is there no way? Otherwise I will have to do this: Add tap for the textblock and use sender as a textblock, then get the value of the text. But I really do not want to use this approach.
I see you use bindings for your textblocks and image. So why don't you use ( if you haven't already done it) an IList instance of class which hold an information about them? Then set this instance as an ItemSource for your listbox. That way when user taps somewhere on listbox you can catch the SelectedIndex or SelectedItem of a listbox item. And this will help you to figure out which element of IList collection to extract so you could get your text or image or whatever you need.
And you don't need to use GestureServices from external Silverlight Toolkit with Mango. Tap, DoubleTap etc. are built-in.

AutoCompleteBox in silverlight toolkit for windows phone wrong display

I'm having a problem with the autocompletebox from the toolkit for windows phone. I bind it to some data, then when i press it and start typing, it discovers some items but they are displayed wrong (the list is shown separated from the box, and also if i click on any item, nothing happens. If i click where the item would be supposed to be (for example, right on the top of the box), then it gets selected. It looks like a rendering problem (bug?)) but perhaps i'm doing something wrong. Here's the code for the box :
<DataTemplate x:Key="DataTemplate1">
<ContentControl Content="{Binding Name}" Margin="8,7"/>
</DataTemplate>
<toolkit:AutoCompleteBox ItemsSource="{Binding}" x:Name="txtSelectValues" MinWidth="250" Margin="0,0,0,0" ItemTemplate="{StaticResource DataTemplate1}" VerticalAlignment="Top" />
Found it. It's a bug with the AutoCompleteBox. When inside a scrollviewer control, the dropdown gets messed up and displayed in an incorrect position
Its not just that is also to do with being placed inside of a Pivot/Panaroma as well as the scrollviewer, the silverlight gurus have stated they haven't a timeline for the fix for the Pivot control, and there is a nasty hack
http://silverlight.codeplex.com/workitem/7574
I think the answer might just be that you shouldn't be using a ContentControl directly used like this. Try using something like a TextBlock instead - e.g.:
<DataTemplate x:Key="DataTemplate1">
<TextBlock Text="{Binding Name}" Margin="8,7"/>
</DataTemplate>
If that's not the answer, then try pulling back to a simple example - especially removing all the Margin's, Width's, Alignment's, etc - then put them back in one-by-one to work out and understand what is causing the effect you are seeing.

Categories