Displaying images in a grid - wpf

I have an ObservableCollection of BitmapSources and I want to display them all in a grid and override the selected and not selected styles. I have been looking at a lot of different ways to do this but have not managed to get it working to my satisfaction. My latest attempt looks something like this:
<ListBox ItemsSource={Binding Images}>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel ItemsHost="True">
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="3">
<Image Source={Binding}>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This does display the images, but the WrapPanel is always just one row... I don't want to have to scroll horizontally, so I want it to make row breaks by itself or be able to tell it that it should only have 3 items per row or something like that. Without the WrapPanel the images take one row each. Also, I don't really understand how to override the style for selected items and such since the DataTemplate's DataType is BitmapSource now, not ListBoxItem...
I have also tried a DataGrid (which seems more appropriate) with similar results.
How do I do this?

Define UniformGrid as your ItemsPanel and set the Columns value to the number of items you want to have per row.
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
And your style to override default selection background and border brush setting:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Transparent"/>
</Style.Resources>
<Setter Property="BorderThickness" Value="0.5"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="blue"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="BorderBrush" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>

You could simply disable horizontal scrolling. This will make the WrapPanel wrap its children.
<ListBox ItemsSource="{Binding Images}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="3">
<Image Stretch="None" Source="{Binding}"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

-Edit-
The part about listbox width in this answer is not correct, see clemens answer instead
-Original post-
You need to constrict the width of the list box, either by setting the width or with some other parent layout. Otherwise the wrap panel will claim all the available width to put everything on one row. This happens to me alot when using scrollpanes for example
One tip is to setting different background colors to elements to see who is actually grabbing the space
You can change the selected styles by setting the ItemsContainerStyle

Related

Click/focus on a ListBoxItem's content doesn't bubble up

I've got a ListBox that's declared like this:
<ListBox ItemsSource="{Binding Contracts}" SelectedItem="{Binding SelectedContract}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem Content="{Binding Name}">
<ListBoxItem.ToolTip>
<Grid>
[code omitted for reasons of clarity]
</Grid>
</ListBoxItem.ToolTip>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I expected the normal selection behavior since I play with the item's ToolTip rather than its content structure.
However, clicking an item's name doesn't focus/select that item. Only by clicking that tiny space between each item (easiest way would be the space between an item's name and the ListBox's border) the item gets focused/selected.
Of course, I googled around and thought I'd found the culprit (event doesn't bubble up). But any solution provided here on SO or elsewhere, e. g. adding code like this:
<ListBoxItem.Style>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</ListBoxItem.Style>
turned out to not solve the problem. So, I assume I do something wrong and I'm just too blind to see it. And while there might be solutions using code-behind, I prefer to stick with clean and pure XAML.
Please help me, understanding my mistake and solving it.
if the purpose is add ToolTip for ListBoxItem, you can use ItemContainerStyle. ListBox creates ListBoxItems for each databound item, adding ListBoxItem into
DataTemplate isn't necessary, if it breaks some functionality, try to avoid it
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="ToolTip">
<Setter.Value>
<Grid>
<TextBlock Text="{Binding .}"/>
</Grid>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
edit: I used Snoop app to check your variant with ListBoxItem in DataTemplate. There is 2 ListBoxItems in visual tree of each ListBox element, maybe one of prevent selection of another

Excess border selection in WPF's Lisbox [duplicate]

I have a ListBox in which each item is a StackPanel. The StackPanel consist of an Image and a TextBlock below it:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding Path=ImageFilePath}"/>
</Image.Source>
</Image>
<TextBlock Text="Title" TextAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
It looks like this:
When the user select an item, I get the default blue rectangle that surround the StackPanel:
Now, I want to make a different border for the selected-item, but I want it to surround only the image.
I know how to make a control template and put a custom border around the ContentPresenter, but this, of course, will surround the whole StackPanel, not only the Image.
I don’t know if making changes to the ContentPresenter is possible, and if it is a good idea at all. If there is other way to achieve the look I want, it will be fine as well.
Right, the ListBox's own ContentPresenter isn't helpful for what you're doing. You want to a) eliminate the ListBox's own selection visuals and b) replace them with something more suitable in the DataTemplate for your items.
The default selection visual is applied by the default template for ListBoxItem. So replace that template. Using a Style in the resources for your ListBox, apply your own control template to ListBoxItem. Not much to it, just present the content and don't provide a selection background. Then you handle the selection visuals with a trigger in your data template, where your image and your label are defined and you can apply changes to one and not the other. The below example works for me.
Note that there's some fiddling with the HorizontalAlignment on the Border element to make it cling to the Image element within it. Also, I wrote a quickie test viewmodel whose Items property is called Items; I assume this is not the name of the collection member you're using to populate your own ListBox.
<ListBox
Margin="8"
ItemsSource="{Binding Items}"
>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border
x:Name="HighlightBorder"
BorderThickness="4"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10"
>
<Border.Style>
<Style TargetType="Border">
<!-- MUST set default BorderBrush via a style, if you set it at all.
As an attribute on the Border tag, it would override the effects of
the trigger below.
-->
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
</Border.Style>
<Image Source="{Binding ImageFilePath}" />
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="True">
<Setter TargetName="HighlightBorder" Property="BorderBrush" Value="Orange" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF: How to make a Expander overflow and fill window

I'm trying to create a expander that has a togglebutton/header as a slim bar to the left but when expanded fills over the rest of the window, even over material that's already there.
I'm not really sure what the best way to do it is. I thought perhaps of a grid with 2 columns. First would have the expander, second the other material. Next I would have a trigger that would set the second column width to zero when the Expander IsExpanded.
I'm not really sure how to get that to work or even how to do it properly.
Here is some code example:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Name="SecondColumn" Width="*" />
</Grid.ColumnDefinitions>
<Expander ExpandDirection="Right" IsExpanded="True">
<Expander.Resources>
<Style TargetType="Expander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Expander" >
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True" >
<Setter TargetName="SecondColumn" Property="ColumnDefinition.Width" Value="0" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Expander.Resources>
<ListBox >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Expander>
<TabControl Grid.Column="1" />
</Grid>
I wan't the listbox to be seen when expanded, otherwise the TabControl
Any ideas?
It sounds like you're wanting to do something similar to Karl Shifflett's example here. He's just modifying the z-index of the content control in this case and setting the row height manually to give the illusion of a popup, so you'd want to make sure you're not trying to ZIndex other visual elements similarly.
You will want to make sure you're setting ColumnSpan and RowSpan on your Expander so that when it does expand it covers the content of those rows.

TemplateBinding in ItemsPanelTemplate

I'm building a custom ItemsControl in Silverlight that (amongst other things) allows items to be displayed horizontally or vertically at runtime. How can I bind the Orientation property of the ItemsPanel to the Orientation property of my parent control? I've tried using TemplateBinding (which works inside the ControlTemplate) but does not seem to work inside the ItemsPanelTemplate, am I doing something wrong?
<Style TargetType="CustomItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="{TemplateBinding Orientation}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
Use a RelativeSource:
<Style TargetType="CustomItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="{Binding Orientation, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type CustomItemsControl}}}" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
Edit after comment: Silverlight doesn't support RelativeSource, but this post by Colin Eberhardt explains how it can be implemented manually.

In WPF, how can I add a border to a control in code behind?

In my XAML I want to dynamically generate a ListBox with the following:
<ListBox Name="MainListBox">
<Border Style="{DynamicResource ListBoxItemRoundedBorder}">
<ListBoxItem >
<TextBlock>
Some Text Here
</TextBlock>
</ListBoxItem>
</Border>
<Border Style="{DynamicResource ListBoxItemRoundedBorder}">
<ListBoxItem >
<TextBlock>
Some Text Here
</TextBlock>
</ListBoxItem>
</Border>
<Border Style="{DynamicResource ListBoxItemRoundedBorder}">
<ListBoxItem >
<TextBlock>
Some Text Here
</TextBlock>
</ListBoxItem>
</Border>
</ListBox>
I want to add items to this listbox via code behind. How can I add the item and the border via code behind. I can add the list box items easy enough but can't seem to figure out the border:
For Each s As String in MyArray
Dim lbi as New ListBoxItem()
Dim tb as New TextBlock()
tb.Text = s
lbi.content = tb
MainListBox.Items.Add(lbi)
Next
Edit: To clear up any confusion I want a border around each of the ListBox Items. I've updated the XAML - effectively I want to render that XAML dynamically, or equivalent, via code behind. I already have the border style defined.
Have you looked in to Templating the ListBoxItem
Use this to get the border effect you're looking for
<Style x:Key="ListBoxItemRoundedBorder" TargetType="ListBoxItem">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border
Name="Border"
Padding="2"
SnapsToDevicePixels="true" Style="{DynamicResource RoundedBorder}">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Border" Property="Background"
Value="{StaticResource SelectedBackgroundBrush}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground"
Value="{StaticResource DisabledForegroundBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then on your listbox use
<ListView ItemContainerStyle="{StaticResource ListBoxItemRoundedBorder}" />
Although, based on your question, I can't exactly see what design your looking for. Are you looking for a List with a border around it or a list with a border around each item?
I don't understand. If you want one Border, why not just stick it on the outside of the ListBox? I'll assume you want one Border per ListBoxItem. In that case, just modify the ItemTemplate:
<ListBox>
<ListBox.ItemTemplate>
<Border>
<TextBlock Text="{Binding}"/>
</Border>
</ListBox.ItemTemplate>
</ListBox>
"I want to add items to this listbox via code behind."
I have just built a page that loads controls to the page dynamically (based on a collection).
So to answer the the question... you must apply the settings (like what is done with the templating in xaml) in your code. Here is an example in C#: (in vb the first line would start with Dim listBoxStyle as Style...)
Style listBoxStyle = new System.Windows.Style(typeof(ListBox));
listBoxStyle.Setters.Add(new Setter(ListBox.BorderThicknessProperty, new Thickness(0,0,0,0)));
ListBox rdoList = new ListBox();
rdoList.Resources.Add(typeof(ListBox), listBoxStyle);
Notice the thickness(). I have mine set to no border as it defaults to having a border. You can do this with your textboxes and just add the thickness like (1,1,1,1).
Don't know how your calling your dynamic controls from code but you may want to view this post for an easy way to access dynamic wpf controls by name value from code.

Resources