Changing opacity in a long listbox make last elements disapear - silverlight

I created a listbox with an Opacity set to a value less than 1, and when I bind a long list, the last elements disappear.
I created a little sample to reproduce the problem
In the XAML, a listbox :
<ListBox x:Name="mainList" ItemsSource="{Binding}" Opacity="0.5"></ListBox>
and it is bound to a long list :
public MainPage()
{
InitializeComponent();
List<int> l = new List<int>();
for (int i = 0; i < 100; i++)
{
l.Add(i);
}
this.DataContext = l;
}
When I execute it, the last element I see is the "87", there is the place for the others elements in the bottom, but it is completely black.
What is the problem exactly ?
edit: a colleague told me it is probably a problem with virtualization, as the problem happens at the 87, and we have 29 items visible on the screen (the listbox virtualizes 3 times the number of items shown, 3*29 = 87).
I made the same test with an ItemsControl (without virtualization), and the problem is the same.

*Edit - I was able to reproduce your issue - one thing that does not produce exactly the same UI, but works and looks similar, is setting the ListBox opacity to 1, and then set the UI element in the DataTemplate to opacity 0.5.
Code:
<ListBox x:Name="mainList" ItemsSource="{Binding}" Opacity="1">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Opacity="0.5" Text="{Binding}">
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

How to format text in a ListBox where items are added dynamically?

I need to format the text in each textblock, which is an item of my ListBox. I have managed to make something but it doesnt look the same as if I would call the Console.WriteLine-Method.
private void addNewGameItem(string gamename, string lastChangedDate)
{
ListBoxItem lbi = new ListBoxItem();
StackPanel stpl = new StackPanel();
TextBlock tbl = new TextBlock();
tbl.Text = String.Format("{0,-100} {1,30}", gamename, lastChangedDate);
tbl.FontSize = textBlockFontsize;
stpl.Orientation = Orientation.Horizontal;
stpl.Children.Add(tbl);
lbi.Content = stpl;
savedGamesList.Items.Add(lbi);//Add the ListBoxItem to the ListBox
}
The problem is that if the gamename is longer than for example the previous one, the date will appear futher right. How can I format this, that it doesnt matter how long the gamename is, so the date-string will start on the same position, in each textblock?
This might be easier done in XAML rather than the code behind as you can more easily define the DataTemplate to be used to display your list items that way. Make your list of games an ObservableCollection and bind the ItemsSource of your list box to that. This will mean it auto-updates when you add a new item to the list.
Then you can split the string into two parts, one for the game name the other for the date:
<ListBoxItem ...>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding GameName}"/>
<TextBlock Text="{Binding LastChangedDate}"
HorizontalAlignment="Right"/>
</StackPanel>
</DataTemplate>
</ListBoxItem>
Then in the ListBox style definition include the line:
HorizontalContentAlignment="Stretch"
This will make each list box item fill the entire width of the list box and (as long as the dates are formatted so that they all come out the same length) align them correctly.

How to build a grid of controls in XAML?

I am trying to build a UI in WPF to a specification. The UI is for editing a collection of items. Each item has an editable string property, and also a variable number of read-only strings which the UI needs to display. It might look something like this:
or, depending on data, might have a different number of text label columns:
The number of text columns is completely variable and can vary from one to "lots". The specification calls for the columns to be sized to fit the longest entry (they are invariably very short), and the whole thing should look like a grid. This grid will be contained in a window, stretching the text box horizontally to fit the window.
Importantly, the text boxes can contain multi-line text and will grow automatically to fit the text. The rows below need to be pushed out of the way if that happens.
Question: what would be a good way of doing this in WPF?
Coming from a WinForms background, I am thinking of a TableLayoutPanel, which gets populated directly by code I write. However, I need to do this in WPF. While I could still just get myself a Grid and populate it in code, I would really rather prefer a way that's more in line with how things are done in WPF: namely, define a ViewModel, populate it, and then describe the View entirely in XAML. However, I can't think of a way of describing such a view in XAML.
The closest I can get to this using MVVM and XAML is to use an ItemsControl with one item per row, and use a data template which, in turn, uses another ItemsControl (stacked horizontally this time) for the variable number of labels, followed by the text box. Unfortunately, this can't be made to align vertically in a grid pattern like the spec requires.
This does not map all too well, you could probably use a DataGrid and retemplate it to look like this. In other approaches you may need to imperatively add columns or the like to get the layout done right.
(You can hook into AutoGeneratingColumn to set the width of that one writeable column to *)
You can create your own Panel and then decide on how you want the layout logic to work for the children that are put inside it.
Look at this for inspiration:
http://www.nbdtech.com/Blog/archive/2010/07/27/easy-form-layout-in-wpf-part-1-ndash-introducing-formpanel.aspx
You could have a "ColumnCount" property, and then use that within the MeassureOverride and ArrangeOverride to decide when to wrap a child.
Or you could modify this bit of code (I know it's Silverlight code, but it should be close to the same in WPF).
http://blogs.planetsoftware.com.au/paul/archive/2010/04/30/autogrid-ndash-part-1.aspx
Instead of having the same width for all columns (the default is 1-star "*"), you could add a List/Collection property that records the different column widths sized you want, then in the AutoGrid_LayoutUpdated use those widths to make the ColumnDefinition values.
You've asked for quite a bit, the following code shows how to build a grid with the controls you want that sizes as needed, along with setting up the bindings:
public void BuildListTemplate(IEnumerable<Class1> myData, int numLabelCols)
{
var myGrid = new Grid();
for (int i = 0; i < myData.Count(); i++)
{
myGrid.RowDefinitions.Add(new RowDefinition() { Height= new GridLength(0, GridUnitType.Auto)});
}
for (int i = 0; i < numLabelCols; i++)
{
myGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(0, GridUnitType.Auto) });
}
myGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
for (int i = 0; i < myData.Count(); i++)
{
for (int j = 0; j < numLabelCols; j++)
{
var tb = new TextBlock();
tb.SetBinding(TextBlock.TextProperty, new Binding("[" + i + "].labels[" + j + "]"));
tb.SetValue(Grid.RowProperty, i);
tb.SetValue(Grid.ColumnProperty, j);
tb.Margin = new Thickness(0, 0, 20, 0);
myGrid.Children.Add(tb);
}
var edit = new TextBox();
edit.SetBinding(TextBox.TextProperty, new Binding("[" + i + "].MyEditString"));
edit.SetValue(Grid.RowProperty, i);
edit.SetValue(Grid.ColumnProperty, numLabelCols);
edit.AcceptsReturn = true;
edit.TextWrapping = TextWrapping.Wrap;
edit.Margin = new Thickness(0, 0, 20, 6);
myGrid.Children.Add(edit);
}
contentPresenter1.Content = myGrid;
}
A Quick Explanation of the above All it is doing is creating the grid, defines rows for the grid; and a series of columns for the grid that auto size for the content.
Then it simply generates controls for each data point, sets the binding path, and assigns various other display attributes along with setting the correct row/column for the control.
Finally it puts the grid in a contentPresenter that has been defined in the window xaml in order to show it.
Now all you need do is create a class with the following properties and set the data context of the contentPresenter1 to a list of that object:
public class Class1
{
public string[] labels { get; set; }
public string MyEditString { get; set; }
}
just for completeness here is the window xaml and constructor to show hooking it all up:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<ContentPresenter Name="contentPresenter1"></ContentPresenter>
</Window>
public MainWindow()
{
InitializeComponent();
var data = new List<Class1>();
data.Add(new Class1() { labels = new string[] {"the first", "the second", "the third"}, MyEditString = "starting text"});
data.Add(new Class1() { labels = new string[] { "col a", "col b" }, MyEditString = "<Nothing>" });
BuildListTemplate(data, 3);
DataContext = data;
}
You can of course use other methods such as a listview and build a gridview for it (I'd do this if you have large numbers of rows), or some other such control, but given your specific layout requirements probably you are going to want this method with a grid.
EDIT: Just spotted that you're looking for a way of doing in xaml - tbh all I can say is that I don't think that with the features you're wanting that it is too viable. If you didn't need to keep things aligned to dynamically sized content on seperate rows it would be more viable... But I will also say, don't fear code behind, it has it's place when creating the ui.
Doing it in the code-behind is really not a WPFish(wpf way).
Here I offer you my solution, which looks nice imo.
0) Before starting, you need GridHelpers. Those make sure you can have dynamically changing rows/columns. You can find it with a little bit of google:
How can I dynamically add a RowDefinition to a Grid in an ItemsPanelTemplate?
Before actually implementing something, you need to restructure your program a little. You need new structure "CustomCollection", which will have:
RowCount - how many rows are there(implement using INotifyPropertyChanged)
ColumnCount - how many columns are there(implement using INotifyPropertyChanged)
ActualItems - Your own collection of "rows/items"(ObservableCollection)
1) Start by creating an ItemsControl that holds Grid. Make sure Grid RowDefinitions/ColumnDefinitions are dynamic. Apply ItemContainerStyle.
<ItemsControl
ItemsSource="{Binding Collection.ActualItems,
Converter={StaticResource presentationConverter}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
local:GridHelpers.RowCount="{Binding Collection.RowCount}"
local:GridHelpers.StarColumns="{Binding Collection.ColumnCount,
Converter={StaticResource subtractOneConverter}"
local:GridHelpers.ColumnCount="{Binding Collection.ColumnCount}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="Grid.Row" Value="{Binding RowIndex}"/>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
The only thing left to do: implement presentationConverter which converts your Viewmodel presentation to View presentation. (Read: http://wpftutorial.net/ValueConverters.html)
The converter should give back a collection of items where each "label" or "textbox" is a seperate entity. Each entity should have RowIndex and ColumnIndex.
Here is entity class:
public class SingleEntity
{
..RowIndex property..
..ColumnIndex property..
..ContentProperty.. <-- This will either hold label string or TextBox binded property.
..ContentType..
}
Note that ContentType is an enum which you will bind against in ItemsTemplate to decide if you should create TextBox or Label.
This might seem like a quite lengthy solution, but it actually is nice for few reasons:
The ViewModel does not have any idea what is going on. This is purely View problem.
Everything is dynamic. As soon you add/or remove something in ViewModel(assuming everything is properly implemented), your ItemsControl will retrigger the Converter and bind again. If this is not the case, you can set ActualItems=null and then back.
If you have any questions, let me know.
Well, the simple yet not not very advanced way would be to fill the UI dynamically in the code-behind. This seems to be the easiest solution, and it more or less matches your winforms experience.
If you want to do it in a MVVM way, you should perhaps use ItemsControl, set the collection of items as its ItemsSource, and define a DataTemplate for your collection item type.
I would have the DataTemplate with something like that:
<Window x:Class="SharedSG.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:app="clr-namespace:SharedSG"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type app:LabelVM}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="G1"/>
<ColumnDefinition SharedSizeGroup="G2"/>
<ColumnDefinition MinWidth="40" Width="*"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding L1}" Grid.Column="0"/>
<Label Content="{Binding L2}" Grid.Column="1"/>
<TextBox Grid.Column="2"/>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid Grid.IsSharedSizeScope="True">
<ItemsControl ItemsSource="{Binding}"/>
</Grid>
</Window>
You're probably way past this issue, but I had a similar issue recently and I got it to work surprisingly well in xaml, so I thought I'd share my solution.
The major downside is that you have to be willing to put an upper-bound on what "lots" of labels means. If lots can mean 100s, this won't work. If lots will definitely be less than the number of times you're willing to type Ctrl+V, you might be able to get this to work. You also have to be willing to put all the labels into a single ObservableCollection property in your view model. It sounded to me in your question that you already tried that out anyway though.
I takes advantage of AlternationIndex to get the index of the label and assign it to a column. Think I learned that from here. If an item has < x labels the extra columns won't get in the way. If an item has > x labels, the labels will start stacking on top of each other.
<!-- Increase AlternationCount and RowDefinitions if this template breaks -->
<ItemsControl ItemsSource="{Binding Labels}" IsTabStop="False" AlternationCount="5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Grid.Column"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(ItemsControl.AlternationIndex)}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid IsItemsHost="True">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="A"/>
<ColumnDefinition SharedSizeGroup="B"/>
<ColumnDefinition SharedSizeGroup="C"/>
<ColumnDefinition SharedSizeGroup="D"/>
<ColumnDefinition SharedSizeGroup="E"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

Wpf Xbap TreeView VirtualizingStackPanel bug?

I have a TreeView with VirtualizingStackPanel on. The TreeView has a "Level 1" TreeViewItem, and I bind this TreeViewItem to a 10k items list. Each children is also another TreeViewItem.
Virtualization works well, and the performance is great, but there is a big issue. Let's say I am at the top of the page and press Ctrl-End to get to the bottom, browser goes blank. It will reappear if I scroll the mouse a bit or resize the browser.
Another big issue is: when i scroll very fast to the middle or the bottom of the tree. let's say I stop at item number 5000. Then I can't expand the child treeviewitem, the browser just
shows nothing until I scroll or resize a bit.
Any help is very much appreciated. Below is the sample xaml & code:
<Page x:Class="WpfBrowserApplication3.Page1"
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:s="clr-namespace:WpfBrowserApplication3"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="Page1" DataContext="{StaticResource _mainViewModel}">
<Page.Resources>
<s:SingleToCollectionConverter x:Key="_collectionConverter"></s:SingleToCollectionConverter>
<DataTemplate x:Key="_level2Template">
<TreeViewItem Header="{Binding Order}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Order: "></TextBlock>
<TextBox Text="{Binding Order}"></TextBox>
</StackPanel>
</TreeViewItem>
</DataTemplate>
</Page.Resources>
<TreeView VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Standard">
<TreeViewItem Header="Level 1" ItemsSource="{Binding Level2List}" ItemTemplate="{StaticResource _level2Template}"></TreeViewItem>
</TreeView>
</Page>
public class MainViewModel
{
public MainViewModel()
{
Level2List = new List<Level2>();
for (int i = 0; i < 10000; i++)
{
Level2List.Add(new Level2(i));
}
}
public List<Level2> Level2List { get; set; }
}
public class Level2
{
public Level2(int order)
{
Order = order;
}
public int Order { get; set; }
}
I use Visual Studio 2010 with .Net 4. Plus I did notice if I set the Height & Width for the TreeViewItem under _level2Template, the issue is gone. But setting the Height is not an option in my case, cause the Height varies in the real Application.
Updated: It seems quite obvious to me that this issue happened because the height of the child treeviewitem may vary. Perhaps that's the reason why VirtualizingStackPanel is not turned on by default in TreeView, but it is turned on by default in DataGrid & ListBox. Needless to say the Height of a datagrid or listbox item is usually unchanged.
Updated: I downloaded a free trial of telerik RadTreeView and tested the virtualization. This problem does not appear at all in telerik radtreeview. May test the telerik one a little more then probably go with it then.
Found this: TreeView Virtualization
Same issue, and no solution for TreeView. Only way around is to use the ListBox instead of TreeView as suggested in http://www.beacosta.com/blog/?p=45
Pretty sure this is TreeView bug. I have tried Telerik RadTreeView and it also has its own bug when turn on Virtulization. I will change to use ListBox instead.

Silverlight combobox performance issue

I'm facing a performance issue with a crowded combobox (5000 items). Rendering of the drop down list is really slow (as if it was computing all items before showing any).
Do you have any trick to make this dropdown display lazy?
Xaml code:
<Grid x:Name="LayoutRoot">
<StackPanel Orientation="Horizontal" Width="200" Height="20">
<TextBlock>Test Combo </TextBlock>
<ComboBox x:Name="fooCombo" Margin="5,0,0,0"></ComboBox>
</StackPanel>
</Grid>
code behind:
public MainPage()
{
InitializeComponent();
List<string> li = new List<string>();
int Max = 5000;
for (int i = 0; i < Max; ++i)
li.Add("Item - " + i);
fooCombo.ItemsSource = li;
}
Well, there seems to be a bug in the Combobox UI virtualization, so an autocompletebox should be the way to go.
If you want an actual ComboBox (and not an AutoCompleteBox) that did this you could replace the ItemsTemplate with a VirtualizingStackPanel. In your example this would look like:
<ComboBox x:Name="fooCombo" Margin="5,0,0,0">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel></VirtualizingStackPanel>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
As a guide though, I'd probably review your usage scenario to see whether or not a ComboBox is the correct control for you - since 5000 items seems like a mighty lot for a drop down list.
By the way, the slowness is expected behavior in Silverlight and not a bug.
Use the AutoCompleteBox instead, adjust the number of characters that need to be entered before a drop down list will be filled to limit how many drop down items are needed at any one time.

WPF DataGrid ComboBox Column: Propagating Header Combobox throughout column

Hey there! Here's my question:
I've got a Datagrid in WPF and I have a first column that is a DataGridComboBoxColumn.
What I'd like to do is to have a header for that column also with a combobox: altering the header with propagate throughout the column.
I can get this done visually, but when I submit the data, the list that is bound with the Datagrid does not carry the new combobox values.
<dg:DataGridComboBoxColumn SelectedItemBinding="{Binding BIBStatus}"
ItemsSource="{Binding Source={StaticResource typeStatus}}"
Width="0.20*">
<dg:DataGridComboBoxColumn.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="Built-In Bridge"/>
<ComboBox SelectedItem="{Binding BIBStatus}"
SelectionChanged="ComboBox_SelectionChanged"
ItemsSource="{Binding Source={StaticResource typeStatus}}"/>
</StackPanel>
</DataTemplate>
</dg:DataGridComboBoxColumn.HeaderTemplate>
</dg:DataGridComboBoxColumn>
In the ComboBox_SelectionChanged I have the following code:
DataGridColumn comboCol = this.gridResults.Columns[0];
for (int i = 0; i < this.gridResults.Items.Count; i++)
{
ComboBox myCmBox = (comboCol.GetCellContent(this.gridResults.Items[i]) as ComboBox);
myCmBox.SelectedValue = ((ComboBox)sender).SelectedValue;
}
When I submit the data, I submit the list that is DataContext to the Datagrid; if I change the value of the first column addressing a row at a time, i.e. clicking the cell with the combobox in each row, the values are propagated to the list in DataContext, however if I change the value of the first column by the header it does not.
Can anyone tell me what I'm missing? I'm guessing it's the way I affect each row, but I've tried SelectedValue, SelectedItem and SelectedIndex... all to no avail.
Thanks in advance...
I think I may have solved it... or at least disguised it...
DataGridColumn comboCol = this.gridResults.Columns[0];
for (int i = 0; i < this.gridResults.Items.Count; i++)
{
ComboBox myCmBox = (comboCol.GetCellContent(this.gridResults.Items[i]) as ComboBox);
myCmBox.SelectedItem = ((ComboBox)sender).SelectedItem;
}
if (this._results != null)
{
foreach (Device device in _results)
{
device.BIBStatus = (TypeStatus)Enum.ToObject(typeof(TypeStatus), ((ComboBox)sender).SelectedValue);
}
}
I tried to change only the datacontext and hope for the two-way binding to work, but it only did when I focused on each row's combobox. So I thought: "why not both ways?"
As you can see, I change both each combobox's selecteditem and each device's BIB status (the datacontext part). This way I get the desired effect.
However I do not think this solution is the best one and I'm still hoping there is a way I can do this without being a scoundrel.
Any suggestions are still welcome!

Resources