Displaying dynamic data in an ItemsControl as a responsive grid - wpf

I'm writing an app that'll allow me to monitor the operational status of websites that I host for my business and clients to maintain a decent uptime on the sites.
I've got most of the app done, but I've hit a bit of a layout snag in rendering the status for each site on the UI which I'm doing with an ItemsControl:
<ItemsControl ItemsSource="{Binding Sites, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<materialDesign:Card Margin="5" Width="220" Padding="5">
<StackPanel>
<TextBlock HorizontalAlignment="Center"
FontSize="20"
Style="{DynamicResource MaterialDesignHeadlineTextBlock}"
Text="{Binding Name}"
TextWrapping="WrapWithOverflow"/>
<materialDesign:PackIcon
HorizontalAlignment="Center" Kind="Web"
Visibility="{Binding OkayIconVisibility}"
Height="50" Width="50" />
<materialDesign:PackIcon
HorizontalAlignment="Center" Kind="Alert"
Visibility="{Binding AlertIconVisibility}"
Height="50" Width="50" />
<TextBlock HorizontalAlignment="Center"
Text="{Binding Status}"
TextWrapping="WrapWithOverflow"/>
</StackPanel>
</materialDesign:Card>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This works except that it only stacks them on top of each other.
What I'd prefer is a horizontal stack that wraps with overflow - so to speak.
The Window is MinWidth="450" so there could be at least 2 sites listed on a row and sites 3 and 4 go beneath them.
I've seen a few posts on creating a custom ItemsControl or setting up a grid as a template for the ItemsControl but those mostly work with a fixed number of items which makes it easier.
I'm very much at a loss as to how I should proceed here. If I could set up a container for the items within the ItemsControl that would suit me perfectly because I can at least work with that. As it is, I don't see how to go about this.

You can use WrapPanel as ItemsControl.ItemsPanel like this:
<ItemsControl ItemsSource="{Binding Sites, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
...
</ItemsControl>
This will stack your Site items horizontally and wraps items when it overflows as you prefer it.

Related

StackPanels going side by side

I have this small stackpanel that gathers information from a class I have. I want the stack panels to go side by side instead of just stacking up on top of each other. This is the code I have
<SplitView.Content>
<Grid>
<ListView x:Name ="View">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<StackPanel>
<TextBlock Text="{Binding title}"></TextBlock>
<TextBlock Text="{Binding location}"></TextBlock>
<TextBlock Text="{Binding date}"></TextBlock>
<TextBlock Text="{Binding desc}"></TextBlock>
<Button HorizontalAlignment="Right" FontFamily="Segoe MDL2 Assets" Content=""></Button>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</SplitView.Content>
I don't want them to go infinitly to the side though, just like 2 next to each other and then another 2 bellow, if that is possible
You can set ListView.ItemsPanel to a StackPanel with Horizontal Orientation.
<ListView x:Name ="View">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>

Improve performance of loading images into ComboBox

I am using WPF ComboBox to populate a list of countries as following:
Inside ComboBox I have a VirtualizingStackPanel that contains an image and a TextBlock:
<ComboBox.ItemTemplate>
<DataTemplate>
<VirtualizingStackPanel Orientation="Horizontal">
<Image Width="30" Height="30" Margin="0" Source="{Binding code, Converter={StaticResource ImageComboBoxConverter}}" VerticalAlignment="Center"/>
<TextBlock Margin="5" Text="{Binding country_Text}"/>
</VirtualizingStackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
I am using a simple converter for DataBinding to retrieve my flag files.
The problem is, the list takes around good 3-4 seconds to populate. Can I apply some kind of buffering or caching technique for images to load faster?
You need to virtualize the ComboBox.ItemsPanel, not the .ItemTemplate. The ItemsPanel is the panel used to display all controls, while the ItemTemplate is used to display each control.
All the Virtualized StackPanel does is not render items which are not visible. Instead, it only renders visible items (plus a few extras for a scroll buffer), and will replace the data behind each item as you scroll. So using it to display each individual item is useless, and you need to instead use it as the panel to display all items.
Your code should probably look something like this :
<ComboBox ItemsSource="{Binding Countries}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Width="30" Height="30" Margin="0" Source="{Binding code, Converter={StaticResource ImageComboBoxConverter}}" VerticalAlignment="Center"/>
<TextBlock Margin="5" Text="{Binding country_Text}"/>
</StackPanel >
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
You may also need to add one or all of these properties to the <ComboBox> tag too.
<ComboBox VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
ScrollViewer.CanContentScroll="True" ...>

What XAML control should I use to keep all child elements within its borders in WPF?

I'm having a UI design issue, this is what I want the contents of my app window to shrink to fit the smallest dimension of the window.
Using the XAML below, when the window is too narrow the contents are shrunk to fit. Perfect. My problem is when the window is too short, the contents fall out the bottom, like this:
<ItemsControl ItemsSource="{Binding GroupStatsDisplayList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Background="Red" Margin="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Viewbox Margin="4" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Width="Auto" Height="Auto" >
<ListBox>
<StackPanel Orientation="Horizontal" Margin="5" Background="Blue">
<Label Content="{Binding GroupID}" FontWeight="Bold"/>
<Label Content="{Binding GroupName}" Width="100" FontWeight="Bold" />
<Label Content="{Binding CallsInQueue}" FontWeight="Bold" />
<Label Content="{Binding TSF}" FontWeight="Bold" />
</StackPanel>
</ListBox>
</Viewbox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have read in answers to similar questions that the ItemsControl uses a StackPanel as its default itemspanel and that the StackPanel does behave the way I'm seeing. A grid has been recommended to overcome issues like mine.
So I tried telling my ItemsControl to use a Grid:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Background="Red" Margin="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
Better because the contents do resize when constrained in either direction. Worse because it seems like there is only one ViewBox->ListBox element that gets updated with the last item in my collection (I can see the three items in the collection cycle through the display as the app starts up). I don't see all items in my collection on screen, only the last one.
I also read that a DockPanel could save me...
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel Background="Red" Margin="5" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
That's when things got crazy and I decided to write this question. Now all items in my collection are there, but they appear one by one when I expand the window horizontally, each new item appears as the existing ones expand to the vertical extent of the window.
How do I get a layout that looks like the first image but will shrink to fit within the smallest dimension of the window?
Why do you have a ListBox in your DataTemplate, when you have only one hardcoded item the StackPanel? To clarify that: The ItemTemplate defines how you want one item to appear in your items Collection. For Example you could create an ItemTemplate which shows an Album Cover on the left, the Artist Name next to it and on the bottom a Star Rating.
A Grid should not be used as an ItemsPanel, because for that you would need to supply a dynamic Grid/Col Definitions collection.
Using a ViewBox is my best advice. But not in the ItemTemplate, this would only size one children, a viewbox around the whole itemscontrol.
This is the XAML that I used based on the answer from dowhilefor.
<Viewbox Margin="3" >
<ItemsControl ItemsSource="{Binding GroupStatsDisplayList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="1" Background="LightGreen">
<Label Content="{Binding GroupID}" FontWeight="Bold"/>
<Label Content="{Binding GroupName}" Width="100" FontWeight="Bold" />
<Label Content="{Binding CallsInQueue}" FontWeight="Bold" />
<Label Content="{Binding TSF}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Viewbox>
I haven't done WPF in a while, and probably it's not considered very good practice, but maybe you could just use a scaletransform on your entire gui ?
I always liked playing around with them.

WPF Nested Databinding; How Do I Bind To An Item Inside Another Item

I have a ListView bound to an ObservableCollection of CustomerContacts.
It works great so far, but being new to this, I'm not sure how to do the next part.
Each customer contact has several contact types, which I want to display under their name.
So inside of CustomerContacts I have another ObservableCollection of ContactTypes.
Here is my current datatemplate:
<DataTemplate x:Key="iconTemplate">
<DockPanel Height="133" Width="150">
<Image Source="/Tracks%203.5;component/Images/NoPic.png" Height="25" Width="25" Margin="1,0" />
<TextBlock DockPanel.Dock="Top" Text="{Binding FullName}" Margin="5,3,5,0" FontWeight="Bold" HorizontalAlignment="Left" />
<<TextBlock Text="{Binding Title}" Margin="5,0,5,3" HorizontalAlignment="Left" />>
</DockPanel>
</DataTemplate>
And here's my first attempt at putting the listview inside:
<DataTemplate x:Key="iconTemplate">
<DockPanel Height="133" Width="150">
<Image Source="/Tracks%203.5;component/Images/NoPic.png" Height="25" Width="25" Margin="1,0" />
<TextBlock DockPanel.Dock="Top" Text="{Binding FullName}" Margin="5,3,5,0" FontWeight="Bold" HorizontalAlignment="Left" />
<ListView ItemsSource="{Binding ContactTypes}">
<ListView.Template>
<ControlTemplate TargetType="ItemsControl">
<ItemsPresenter/>
</ControlTemplate>
</ListView.Template>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Margin="3,0,0,0" HorizontalAlignment="Center" Text="{Binding Path=ContactType}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<!--<TextBlock Text="{Binding Title}" Margin="5,0,5,3" HorizontalAlignment="Left" />-->
</DockPanel>
</DataTemplate>
I want to replace the TextBlock bound to Title with a ListView/ListBox/ItemsControl bound to the items in ContactTypes.
Somewhat similar to this question: < WPF: bind to a List inside a class > but without all the codebehind. It would be nice to have it in the XAML.
There's a couple things I would do here:
Simplify what you're trying to do in order to isolate the problem. Instead of that whole ListView business, just put in there an ItemsControl: <ItemsControl ItemsSource="{Binding ContactTypes}" /> That'll give you the default view of things, which may just show you the data type of your objects (whatever ContactTypes is), but it'll let you know if the binding is working. If it is, you'll get something listed there. If it isn't, you won't.
If you're not getting anything listed, go use Snoop to drill into it and look for errors. Snoop shows the databinding errors, allows you to inspect the DataContext of each of the items, etc.
If you're still having problems, it might benefit us if you posted your class definitions and your code where you wire things up. There might be some other underlying issues (for instance, is the ContactTypes property null when the binding initially occurs and you're not using INPC to let the binding system know when it changes?).

Tab order and index in WPF using the MVVM pattern

I'm having an issue with tabbing through the controls on a WPF application using the MVVM pattern. I have the following XAML which defines a tree structure
<Grid Background="Transparent" Margin="10">
<TreeView ItemsSource="{Binding FirstLevelNavigableViewModels}" Background="Transparent"
HorizontalContentAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderThickness="0"
ItemContainerStyle="{StaticResource TreeViewItemStyle1}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type ViewModel:VendorViewModel}" ItemsSource="{Binding Children}">
<View:VendorView HorizontalContentAlignment="Stretch" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type ViewModel:ProductViewModel}">
<View:ProductView HorizontalContentAlignment="Stretch" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
When the treeview is loaded, the XAML for the "ProductView" is as follows
<Border Margin="0,2,2,2" CornerRadius="3" Background="#3FC7B299" DockPanel.Dock="Right" HorizontalAlignment="Right" Width="109">
<StackPanel Orientation="Vertical" Margin="6,4">
<DockPanel>
<TextBlock DockPanel.Dock="Left" FontFamily="Segoe" FontSize="10" FontWeight="Medium"
Foreground="Black" Opacity="0.75"
Text="CALC. REG. PRICE"></TextBlock>
<Button Width="10" Height="10" Background="Transparent" BorderBrush="Transparent" BorderThickness="0" Padding="-4" Margin="0" Command="{Binding UserDefinedRetailPriceCommand}" Visibility="{Binding UserDefinedRetailPriceButtonView}">
<Image Width="10" Height="10" Source="/Arhaus.Pricing.Client;component/Styles/Images/error.png"></Image>
</Button>
</DockPanel>
<TextBox FontFamily="Segoe" FontSize="16" FontWeight="Medium" KeyboardNavigation.IsTabStop="True" KeyboardNavigation.TabIndex="{Binding RegularPriceTabIndex}"
Foreground="Black" Opacity="0.9" KeyboardNavigation.TabNavigation="Continue"
ebf:LostFocusBehaviour.LostFocusCommand = "{Binding LostFocusSugg}"
Text="{Binding NewSuggestedRetailPrice,Converter={StaticResource FormattingConverter}, ConverterParameter=' \{0:C\}', Mode=TwoWay, UpdateSourceTrigger=LostFocus}" Background="#FFE6DED3" BorderBrush="#FFE6DED3" DataContext="{Binding StringFormat=\{0:c\}, NotifyOnValidationError=True}" Padding="0" TabIndex="1"></TextBox>
</StackPanel>
</Border>
I have the Tab Index bound to an integer that is ever increasing and bound as the treeview is loaded (I.E. I have it setup as tab index 1, 2, 3 etc. as each successive model is loaded).
I would like to be able to hit tab and jump to the next textbox in the treeview but when I click the TAB key, nothing happens. I'm not sure if I have the tabbing setup correctly but I'm very new to WPF and at a loss as to where and how to set the tabbing to make it work. I'm used to WinForms where you just set the tab index and go from there.
Thank you for your assistance, I apologize for the large code block.
I don't have a solution ready but some thoughts that maybe may help:
I don't know what RegularPriceTabIndex returns but probably it begins for each new TreeViewItem at the same index?
The same tab-index is given then multiple times because of repeating use of ProductView. Perhaps this leads to a problem. You can try setting FocusManager.IsFocusScope="true" for the ProductView. Maybe this helps.
Try also to set Control.IsTabStop="true" on the TextBox.

Resources