I have a strange problem regarding my listbox / wrappanel arrow navigation.
My Listbox :
<ListBox x:Name="List"
IsSynchronizedWithCurrentItem="True"
SelectedIndex="{Binding MainIndex, Mode=OneWayToSource}"
SelectedItem="{Binding CurrentFeed, Mode=TwoWay}"
ItemsSource="{Binding CurrentFeedList}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" MaxWidth="{Binding ActualWidth, ElementName=Panel}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="168">
<Border BorderBrush="White" BorderThickness="0" Margin="0,7,0,0">
<Image Source="{Binding Image , Converter={StaticResource ByteArraytoImageSource}}" Width="150" Height="213" ></Image>
</Border>
<Border BorderBrush="White" BorderThickness="0" Height="65" HorizontalAlignment="Center" >
<TextBlock VerticalAlignment="Center" TextWrapping="Wrap" FontFamily="{StaticResource DefaultFontFamily}" FontSize="15" Text="{Binding Title}" Foreground="White" Cursor="Hand" HorizontalAlignment="Center" TextAlignment="Center"/>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The render :
(source: free.fr)
The render is great No problem ! ;)
As you can see it's a list of wallpaper retrieved from RSS.
I have bind a shortcut to make one item readed and then it's removed from the ObservableCollection :
<UserControl.InputBindings>
<KeyBinding Key="D" Modifiers="Control" Command="{Binding ReadCmd}" />
</UserControl.InputBindings>
In My View Model :
ObservableCollection<Feed> CurrentFeedList { get; set; }
private Feed _currentFeed;
public Feed CurrentFeed
{
get { return _currentFeed; }
set
{
_currentFeed = value;
OnPropertyChanged("CurrentFeed");
}
}
public ICommand ReadCmd { get; set; }
public int MainIndex { get; set; }
My ReadCmd is a RelayCommand that call "ReadAction" method.
At the beginning I simply remove the item from the ObservableCollection, but I want to type Ctrl+D twice to read 2 item.
So I decided to get the index of the listbox and select back the same index after removing one.
private void ReadAction()
{
int previousIndex = MainIndex;
if (previousIndex == CurrentFeedList.Count - 1)
previousIndex -= 1;
CurrentFeedList.Remove(CurrentFeed);
CurrentFeed = CurrentFeedList[previousIndex];
}
It's work as I wanted, but one problem remain and I can't solve it.
When I push Ctrl+D the SelectedItem is remove and the next item become Selected. But then when I use the arrow key navigation to navigate over the list, it jump each time to the first item of the list.
I hope it's enough clear.
Thanks you.
This issue is due to the fact that when you are removing the items using the KeyBinding, ListBox loses the focus and hence pressing the arrow keys after that places focus on first item of the ListBox.
So in order to make your arrow keys work from the selected item you will also have to set the focus on Selected Item of the Listbox.
There are couple of ways of doing this, e.g using attached behavior to set the focus on the ListBoxItem. Simplest would be to capture the SelectionChanged event of your ListBox and in the handler set the focus on the currently selected item like below:
private void MyListBox_OnSelectionChanged(object sender, RoutedEventArgs e)
{
var item = (ListBoxItem)MyListBox.ItemContainerGenerator.ContainerFromItem(MyListBox.SelectedItem);
if(item !=null)
item.Focus();
}
Related
I want to make horizontal ListBox with customized item template, so I make a basic template of it.
However, I couldn't find an example of binding 'things' to that WPF XAML, especially with ListBox filled with customized items.
I simply want to dynamically add/remove items in the ListBox with Image, Label, ComboBox with previously filled with number 1 to 10.
the ADD/REMOVE button will be placed outside WPF control, it means that the buttons will be on the Main Window Form.
Also, there are TextBox and picture selector in the Main Window Form so that I can change the text and image.
Below is code behind XAML :
Public Class listSequence
Public Sub New()
InitializeComponent()
listbox.Items.Add("hi")
listbox.Items.Add("there")
End Sub
End Class
Below is XAML :
<ListBox Name="listbox" VerticalContentAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Padding="10" Margin="5" BorderThickness="1" BorderBrush="Aqua" CornerRadius="0" Width="120" VerticalAlignment="Stretch">
<StackPanel>
<Image />
<Label Content="{Binding}" />
<TextBlock Text="hi" />
<ComboBox x:Name="cboRepeat" ItemsSource="{Binding}" DisplayMemberPath="RepeatCounter" IsSynchronizedWithCurrentItem="True"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Let's say we have a basic class named Item:
public class Item : INotifyPropertyChanged
{
public string Text { get; set; } // Implement INotifyPropertyChanged
public string ImagePath { get; set; } // properly on these properties
}
And a collection of these in a view model:
public ObservableCollection<Item> Items { get; set; }
Now to display these items in the UI, we use a ListBox and set the ItemsSource property:
<ListBox ItemsSource="{Binding Items}">
</ListBox>
When it comes to defining the ListBox.ItemTemplate, you need to understand that this DataTemplate will be applied to each item and that it has access to all of the properties defined in the Item class:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image ImageSource="{Binding ImagePath}" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Therefore, you can access the properties in the collection class as shown above. You can find out the full story by looking at the ItemsControl.ItemTemplate Property page on MSDN.
I am new into WPF. I am currently developing an application where my solution in MSVC# 2010 has 2 projects. One project(i.e.MSPBoardControl) has a mainwindow.xaml and I have added another wpf window ConnectView.xaml. By using the grid in mainwindow.xaml I am able to add the Connectview.xaml view to my application in mainwindow.xaml.cs. My 2nd project is a classlibrary which has a usercontrol(i.e. Voltage).
Mainwindow.xaml: This grid is the one to which I add my Connectview.xaml UI component
<Grid Grid.Row="0" Name="MainGrid" HorizontalAlignment="Stretch" Style="{DynamicResource styleBackground}" >
</Grid>
I have a listbox in my mainwindow.xaml towards the left side which has list of items(i.e. Voltage, Clock etc). The right side of .xaml has my grid which is shown above(contains the connectview on startup). What I basically need is when I click the item from the listbox, I want to hide the view which is shown on startup(connectview) and make the selecteditem UI component visible.
As mentioned in the beginning I want to make a usercontrol of my classlibrary(VoltageView) visible on clicing "Voltage" item from Listbox.
ListBox in my mainwindow.xaml:
<ListBox Name="ButtonPanel" Style="{DynamicResource styleBanner}" ItemsSource="{Binding BoardOperations, Mode=TwoWay}" SelectedItem="{Binding SelectedList}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Name="BoardTabChanger" Margin="53,27,0,0" Text="{Binding ListName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The ViewModel Class of MSPBoardControl is here:
public class BoardControlViewModel : INotifyPropertyChanged
{
public ObservableCollection<BoardControlModel> m_BoardOperation;
public List<ConnectModel> m_BoardNames;
public BoardControlViewModel()
{
//Adding items to ListBox
}
public List<ConnectModel> BoardNames
{
get; set;
}
private ConnectModel m_SelectedBoardItem;
public ConnectModel SelectedBoard
{
get; set;
}
public ObservableCollection<BoardControlModel> BoardOperations
{
get; set;
}
//GIVES ME THE SELECTED ITEM FROM LISTBOX
private BoardControlModel m_SelectedListItem;
public BoardControlModel SelectedList
{
get
{
return m_SelectedListItem;
}
set
{
m_SelectedListItem = value;
onListSelected(value);
NotifyPropertyChanged("SelectedList");
}
}
private void onListSelected(BoardControlModel SelectedItem)
{
if (SelectedItem.ListName == "Voltage")
{
//HOW CAN I ADD THE VOLTAGEVIEW HERE???
}
}
The above method OnListSelected() retrieves the selecteditem and when I check the condition dat item = voltage, I want to add the voltageview component to my maingrid and hide the connectview.
VoltageView.xaml:
<Grid Name="VoltageControl" Style="{DynamicResource styleBackground}" DataContext="{StaticResource VoltageViewModel}" >
<Label Content="Label" FontSize="15" Foreground="Black" Height="28" HorizontalAlignment="Left" Margin="10,31,0,0" Name="label9" VerticalAlignment="Top" Width="117" />
<Label Content="Set Voltage" FontSize="15" Foreground="Black" Height="28" HorizontalAlignment="Left" Margin="150,31,0,0" Name="label10" VerticalAlignment="Top" Width="117" />
<Label Content="Current Value" FontSize="16" Foreground="Black" Height="28" HorizontalAlignment="Left" Margin="300,31,0,0" Name="label11" VerticalAlignment="Top" Width="117" />
<Label Content="Enable/Disable" FontSize="15" Foreground="Black" Height="28" HorizontalAlignment="Left" Margin="460,31,0,0" Name="label12" VerticalAlignment="Top" Width="127" />
<Button Content="Refresh All" FontSize="13" Height="25" HorizontalAlignment="Left" Margin="230,520,0,0" Name="RefreshBtn" VerticalAlignment="Top" Width="105" />
</Grid>
Please help!!!
I'm going to keep this general(ish). Your best bet is probably to add a property on BoardControlViewModel of type Grid (or whatever UIElement that is common to voltage and connect). As the selection changes, change the UI property to the view you want and hit NotifyPropertyChanged for that property. You can call NotifyPropertyChanged right from onListSelected if you like. Bind the content of whatever grid you're using to this property, prefferably right in the xaml. When the property changes, WPF will notify the grid that its bound content has changed and it will query your new property to see what it should be.
I have five TabItem's in my TabControl and I need to move the position of each tab continuously at runtime. Can anyone tell me how can I change tab index from one position to another position at runtime.
Thanks,
#nagaraju.
Use the below solution:
TabItem tempTab = new TabItem();
tempTab = control.Items[0] as TabItem;
control.Items[0] = control.Items[1];
control.Items[1] = tempTab;
This will definitely work and you have to do from code behind.
If you are using ObservableCollection the you Just have to change the position of the Item in your collection it will be refelected in View...
For Example..
<TabControl ContentTemplate="{StaticResource ResourceKey=listView}"
ItemContainerStyle="{StaticResource ResourceKey=myTabItem}"
ItemsSource="{Binding Path=Persons}"
SelectedItem="{Binding Path=SelectedPerson}"
Style="{StaticResource ResourceKey=myTab}"
TabStripPlacement="Bottom">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="16"
Height="16"
Margin="0,0,2,0"
Source="Themes\Water lilies.jpg" />
<TextBlock Margin="0,4,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
<Button Grid.Row="1" Width="50" Command="{Binding Path=ChangePositionCommand}"> ClickMe </Button>
Here you just Change the Position of the item in TabList in ViewModel and that Position will be changed accordingly...
In Your ViewModel
I have the implementation if getting Data and Setting Up Commands... that up to you how you Do it
public ICommand ChangePositionCommand { get; private set; }
public Person SelectedPerson
{
get { return selectedPerson; }
set
{
selectedPerson = value;
InvokePropertyChanged(new PropertyChangedEventArgs("SelectedPerson"));
}
}
private void ChangePosition(object obj)
{
int index = Persons.IndexOf(SelectedPerson);
if (index <= (Persons.Count-1))
{
Persons.Move(index,index+1);
}
else
{
Persons.Move(index,0);
}
}
The above code my give INdex out of bound but I am no where near an IDE so cant test that you could reapir it according to you.
You need to change the TabControl.Items Collection. Remove the tab from the old Position and set it on a new Positon.
See How to change the order of the TabItem in the wpf TabControl
I need to create a userControl like a Combobox.
In the items in need a TreeView and a Button.
If I navigate the Tree the Item should go to the Text Box in the Bottom.
If I click the Button the Tree should Collapsed.
My First idea was like This but it isn't good.
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Adresse, Mode=TwoWay}" MaxLength="50" MinWidth="170" Grid.Row="5" Margin="5,2,5,2"/>
<Button Width="25" Margin="2" Click="Down">
<Image Source="/C1_net;component/Images/arrow.jpg" HorizontalAlignment="Left" />
</Button>
</StackPanel>
<StackPanel x:Name="Tree" Orientation="Vertical" Visibility="Collapsed">
<sdk:TreeView Height="200" Name="treeView1" Width="200" />
<Button Content="{Binding Path=ApplicationStrings.OKButton, Source={StaticResource ResourceWrapper}}" Width="75" Margin="5" Click="OnOk" HorizontalAlignment="Right"/>
</StackPanel>
</StackPanel>
</Grid>
If I switch to Visible the Control needs more space.
So I need to bring the Tree in Front of the Rest of the Window.
Any ideas?
Ok.
I think you should revisit your interaction.
my problemm wase to think if i select i Item, The Tree wood be closed.
I will navigate in a Hirarchical DataObjekt and Put the Path as selectet Item.
So now I will to somting like this:
public interface ITreeViewItemModel
{
string SelectedValuePath { get; }
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
IEnumerable<ITreeViewItemModel> GetHierarchy();
IEnumerable<string> GetDisplayHierarchy();
IEnumerable<ITreeViewItemModel> GetChildren();
}
and.
public class ComboBoxTreeView : ComboBox
{
private ExtendedTreeView _treeView;
private ContentPresenter _contentPresenter;
public ComboBoxTreeView()
{
this.DefaultStyleKey = typeof(ComboBoxTreeView);
}
......
I found This http://vortexwolf.wordpress.com/2011/04/29/silverlight-combobox-with-treeview-inside/
I need a single scrollable surface that contains two bound lists. At first, I used a ScrollViewer with two ListBox inside, each having their scrolling disabled, so I could still have item selection. Seeing the poor loading time performance, I changed my ListBoxes to ItemsControl, but the performance is still terrible. In total, my two lists have only 110 items.
<ScrollViewer Grid.Row="1">
<StackPanel>
<Button Style="{StaticResource EmptyNonSelectButtonStyle}" BorderThickness="0" HorizontalContentAlignment="Left" Click="AnyCityButton_Click">
<TextBlock Text="{Binding Resources.CurrentLocationItem, Source={StaticResource LocalizedResources}}" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeLarge}" />
</Button>
<TextBlock Text="{Binding Resources.TopTenCitiesHeader, Source={StaticResource LocalizedResources}}" Style="{StaticResource PhoneTextSubtleStyle}" Margin="12,12,12,8" />
<ItemsControl ItemsSource="{Binding TopTenCities}" ItemTemplate="{StaticResource CityDataTemplate}" HorizontalContentAlignment="Stretch" />
<TextBlock Text="{Binding Resources.TopHundredCitiesHeader, Source={StaticResource LocalizedResources}}" Style="{StaticResource PhoneTextSubtleStyle}" Margin="12,12,12,8" />
<ItemsControl ItemsSource="{Binding TopHundredCities}" ItemTemplate="{StaticResource CityDataTemplate}" HorizontalContentAlignment="Stretch" />
</StackPanel>
</ScrollViewer>
What can I do to improve performance? I've tried setting the ItemsSource after the page loading, but it still ugly (empty lists for a few seconds), doesn't make more sense.
Thank you.
This answer has turned into a monster but slog through it and I think you'll find an answer.
We need in some way to use the VirtualizingStackPanel as ListBox. We need to collect all the items to display (the button, the two textblocks and two sets of city data) into a single enumerable of some type. The the real trick and would be to determine one of three templates to use to render the items.
Bottom line is we need to create a new type of ItemsControl. Now we can gain a little advantage by simply accepting we want to create a very specific ItemsControl that supports only this task. First here is a "starter for 10" (a UK media reference).
A really dumb example of creating a specific items control:-
public class SwitchingItemsControl : ItemsControl
{
public DataTemplate AlternativeItemTemplate { get; set; }
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
ContentPresenter cp = (ContentPresenter)element;
if (AlternativeItemTemplate != null && (((int)item) & 1) == 1)
cp.ContentTemplate = AlternativeItemTemplate;
else
cp.ContentTemplate = ItemTemplate;
cp.Content = item;
}
}
This control assumes its items are a set of integers. It has an AlternativeItemTemplate which if supplied it toggles between on an odd/even basis (note that is a facet of the item).
Now lets put that use with a VirtualizingStackPanel:-
<UserControl x:Class="CustomVirtualizingPanelInSL.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SilverlightApplication1"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<local:SwitchingItemsControl x:Name="itemsControl" >
<local:SwitchingItemsControl.Template>
<ControlTemplate TargetType="local:SwitchingItemsControl">
<ScrollViewer VerticalScrollBarVisibility="Visible">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</local:SwitchingItemsControl.Template>
<local:SwitchingItemsControl.ItemTemplate>
<DataTemplate>
<Border CornerRadius="2" BorderBrush="Blue" BorderThickness="1" Margin="2">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding}" />
</Border>
</DataTemplate>
</local:SwitchingItemsControl.ItemTemplate>
<local:SwitchingItemsControl.AlternativeItemTemplate>
<DataTemplate>
<Border CornerRadius="2" BorderBrush="Red" BorderThickness="1" Margin="2">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding}" />
</Border>
</DataTemplate>
</local:SwitchingItemsControl.AlternativeItemTemplate>
<local:SwitchingItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</local:SwitchingItemsControl.ItemsPanel>
</local:SwitchingItemsControl>
</Grid>
</UserControl>
Note the ItemsPanel is using the VirtualizingStackPanel and that gets presented in a ScrollViewer.
Now we can give it lot of content:-
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
itemsControl.ItemsSource = Enumerable.Range(0, 10000);
}
}
}
If you switch to a standard StackPanel this takes ages to load, whereas it appears instant with virtualizing.
Armed with this info you should be able to create a special ItemsControl which has the properties:-
ButtonTemplate (DataTemplate)
HeaderTemplate (DataTemplate)
TopTenHeaderText (String)
TopHundredHeaderText (String)
TopTenSource (IEnumerable<City>)
TipHunderedSource (IEnumerable<City>)
Now you can create a single enumerable with some Linq extension methods:-
itemsControl.ItemsSource = Enumerable.Repeat((object)null, 1)
.Concat(Enumerable.Repeat((object)TopTenHeadeText))
.Concat(TopTenSource.Cast<object>())
.Concat(Enumerable.Repeat((object)TopHundredText))
.Concat(TopHundredSource.Cast<object>())
Now you just need to override PrepareContainerForItemOverride and choose between ButtonTemplate (for the first null item), the HeaderTemplate for item of type string or the ItemTemplate for an item of type City.
Thank you #AnthonyWJones, your answer was (almost) exactly what I was looking for. I've decided to provide my own answer here so that other readers know how I've adapted his answer to my needs.
First, as suggested, I'm deriving from ItemsControl, and providing a second "Template" property, called HeaderTemplate:
#region HeaderTemplate PROPERTY
public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register(
"HeaderTemplate",
typeof( DataTemplate ),
typeof( ItemsControlWithHeaders ),
new PropertyMetadata( null, new PropertyChangedCallback( OnHeaderTemplateChanged ) ) );
public DataTemplate HeaderTemplate
{
get { return ( DataTemplate )this.GetValue( HeaderTemplateProperty ); }
set { this.SetValue( HeaderTemplateProperty, value ); }
}
private static void OnHeaderTemplateChanged( DependencyObject obj, DependencyPropertyChangedEventArgs args )
{
ItemsControlWithHeaders control = obj as ItemsControlWithHeaders;
control.InvalidateArrange();
}
#endregion
Second, I'm overriding PrepareContainerForItemOverride to provide my own template selection logic. What I'm doing is simply redirecting any "string" item to the HeaderTemplate, and other items to the usual ItemTemplate:
protected override void PrepareContainerForItemOverride( DependencyObject element, object item )
{
base.PrepareContainerForItemOverride( element, item );
ContentPresenter presenter = element as ContentPresenter;
if( presenter != null )
{
if( item is string )
{
presenter.ContentTemplate = this.HeaderTemplate;
}
else
{
presenter.ContentTemplate = this.ItemTemplate;
}
}
}
This control can now be used like this:
<local:ItemsControlWithHeaders Grid.Row="1" ItemsSource="{Binding GroupedCities}" ScrollViewer.VerticalScrollBarVisibility="Auto">
<local:ItemsControlWithHeaders.Template>
<ControlTemplate TargetType="local:ItemsControlWithHeaders">
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</local:ItemsControlWithHeaders.Template>
<local:ItemsControlWithHeaders.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</local:ItemsControlWithHeaders.ItemsPanel>
<local:ItemsControlWithHeaders.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Style="{StaticResource PhoneTextSubtleStyle}" Foreground="{StaticResource PhoneAccentBrush}" Margin="12,12,12,8" />
</DataTemplate>
</local:ItemsControlWithHeaders.HeaderTemplate>
<local:ItemsControlWithHeaders.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource EmptyNonSelectButtonStyle}" BorderThickness="0" HorizontalContentAlignment="Left" Click="AnyCityButton_Click">
<TextBlock Text="{Binding Name, Mode=OneWay}" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeLarge}" />
</Button>
</DataTemplate>
</local:ItemsControlWithHeaders.ItemTemplate>
</local:ItemsControlWithHeaders>
To build the data source you must pass to this special hybrid control, LINQ is fine, but I've chosen a much more explicit solution, implemented in my view-model:
public IEnumerable<object> GroupedCities
{
get
{
yield return new CurrentLocationCityViewModel();
yield return Localized.TopTenCitiesHeader; // string resource
foreach( CityViewModel city in this.TopTenCities )
{
yield return city;
}
yield return Localized.TopHundredCitiesHeader; // string resource
foreach( CityViewModel city in this.TopHundredCities )
{
yield return city;
}
}
}
I now have a generic ItemsControlWithHeaders I can reuse in more than just this scenario. Performance is great. The only problem remaining for a purist like me is that the base ItemsControl complains in DEBUG, since an "object" type does not have a "Name" property. It generates a System.Windows.Data Error: BindingExpression path error: 'Name' property not found message in the debug output, which can be ignored.
Could you use one list/itemscontrol, but different datatemplates to get the same effect?
Or you could use a pivot control instead, putting the top 10 sitties in one pivot, top 100 in another pivot..