I've been using design time XAML data for a few weeks now and it seems to work fine except for some cases such as the current one where I want a listbox to display items based on a collection view declared as a resource.
Basically I want to display a list of "Things" grouped by Category.
public class Thing
{
public string Category { get; set; }
public string Name { get; set; }
}
And then I expose this via a dummy class ListOfThings
public class ListOfThings
{
public string ListName { get; set; }
public ObservableCollection<Thing> Things { get; set; }
}
I have a dummy XAML file for the data as follows (XMLFile1.xaml)
<local:ListOfThings xmlns:local="clr-namespace:ListBoxGroupExample"
ListName="TheListOfThings">
<local:ListOfThings.Things>
<local:Thing Name="Item1" Category="Catagory1" />
<local:Thing Name="Item2" Category="Catagory1" />
<local:Thing Name="Item3" Category="Catagory2" />
</local:ListOfThings.Things>
In the window I have a listbox with a group style for an expander for each group
<Window x:Class="ListBoxGroupExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ListBoxGroupExample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
d:DataContext="{d:DesignData Source=/DesignData/XMLFile1.xaml}"
mc:Ignorable="d">
<Window.Resources>
<CollectionViewSource x:Key="GroupedData" Source="{Binding Path=Things}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Category" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<Grid>
<StackPanel>
<ListBox ItemsSource="{Binding Source={StaticResource GroupedData}}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
</Expander.Header>
<ItemsPresenter Margin="5,0,0,0" />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
</ListBox>
</StackPanel>
</Grid>
This works at run time (I create an instance of ListOfThings, populate the Observable collection to have the same structure as the XAML design data) but at design time I get nothing.
If I change the ItemSource to directly bind to the ObservableCollection of Things rather than the collection view, it works but of course I get no grouping.
ItemsSource="{Binding Path=Things}"
Am I misunderstanding something with static resources in that they are not available at design time? Hence why the binding to the collectionview does not render?
Or is the binding syntax to Source incorrect?
Appears to work in Visual Studio/Blend 2013:
Related
My problem is when I'm adding command to button, which is located in ListBoxItem Style in Page.Reosources and click it, nothing happen. But if I add button in default grid, command will work.
XAML:
<Page x:Class="kkRedux.MVVM.View.SpecialView"
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:local="clr-namespace:kkRedux.MVVM.View"
xmlns:viewModel="clr-namespace:kkRedux.MVVM.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="SpecialView">
<Page.DataContext>
<viewModel:SpecialViewModel />
</Page.DataContext>
<Page.Resources>
<Style TargetType="ListBoxItem" x:Key="DieStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Margin="11">
<Button Content="Click Mey"
Width="100" Height="100"
Background="Black"
Command="{Binding DownloadCommand}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ItemsPanelTemplate x:Key="panelTemplate">
<WrapPanel />
</ItemsPanelTemplate>
</Page.Resources>
<Grid>
<!--ItemsSource ommited-->
<ListBox Name="specialListBox"
ItemContainerStyle="{StaticResource DieStyle}"
ItemsPanel="{StaticResource panelTemplate}"
Background="Transparent"/>
<!--this Command is working-->
<Button Content="Click"
Height="100" Width="100"
Command="{Binding DownloadCommand}"/>
</Grid>
</Page>
ViewModel:
internal class SpecialViewModel
{
public RelayCommand DownloadCommand { get; set; }
public SpecialViewModel()
{
DownloadCommand = new RelayCommand<string>(o => Download(o));
}
private void Download(string arg)
{
MessageBox.Show(arg);
}
}
P.S. Click's working fine, but I can't use it due to MVVM.
Bind to the DataContext of the parent Page:
Command="{Binding DataContext.DownloadCommand,
RelativeSource={RelativeSource AncestorType=Page}}"
The default DataContext the element in the ListBox is the current item in the Items collection of the ListBox. That's why your binding doesn't work.
I am trying to create a Windows 10 weather application in WPF using C#. I need to have a Listbox to display recent 10 day weather section. I must set template for Listbox items. I tried this:
<ListBox Grid.Row="1" x:Name="DailyWeatherListBox">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel>
<!--...-->
</StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
This is the recent 10 day weather section (Windows 10 Weather)
Look the Windows 10 Weather. I put this image for disuse Windows 10.
I also don't know how to set Scrollbar in the Listbox corners. I would be very thankful if you could help.
I would start off something like this:
<ListBox ItemsSource="{Binding WeeklyWeather}"
SelectedItem="{Binding SelectedDailyWeather, Mode=TwoWay}">
//Use ItemTemplate to set how item looks "inside"
//I'll leave design details for you
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Day}"/>
<Image Source={Binding WheatherPicturePath}/>
<TextBlock Text="{Binding Temperature}"/>
<TextBlock Text="{Binding Description}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
//ItemsPanel defines container for items. It can be StackPanel, Wrapanel, Grid, etc
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel IsItemsHost="True" Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
//You use this place to design how container normally looks like
<Border Background="White">
//DataTemplate defined above is placed in ContentPresenter
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
//Here we catch "IsSelected" event and re-design our template to visually reflect "selected"
<Trigger Property="IsSelected" Value="true">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Background="Gray">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Here are couple ideas how for those bindings.
public class WeatherViewModel
{
public string Day { get; set; }
public string WheatherPicturePath { get; set; }
public string Temperature { get; set; }
public string Description { get; set; }
}
public class BindedDataContext
{
public ObservableCollection<WeatherViewModel> WeeklyWeather { get; set; }
public WeatherViewModel SelectedDailyWeather { get; set; }
//...
}
Your approaches for code-behind may differ, but they need to be in place for you to use those bindings.
For such scrollbar I would look into Change scrollviewer template in listbox
You can set a simple ListBox template like this:
<ListBox Grid.Row="1" x:Name="DailyWeatherListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<!--Insert XAML for One Item-->
<Label Content="{Binding}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBoxItem>Item 1</ListBoxItem>
<ListBoxItem>Item 2</ListBoxItem>
</ListBox>
In most real world scenarios where there is more then once piece of information per item to be displayed you would define how you want your data to be displayed through a DataTemplate. For example if I wanted to display both the high temperature and the low temperature and style them separately: I would first create a DailyWeather model in c# and create a DataTemplate for it, like so:
public class DailyWeather
{
public int High { get; set; }
public int Low { get; set; }
// You Would Add All Your Other Data You Want to Display Here
}
In your page resources (or another resource dictionary like in App.xaml):
<Window.Resources>
<DataTemplate DataType="{x:Type DailyWeather}">
<Grid>
<StackPanel>
<Label FontSize="18" Content="{Binding High}"/>
<Label FontSize="14" Content="{Binding Low}"/>
</StackPanel>
</Grid>
</DataTemplate>
</Window.Resources>
On your ListBox no ItemTemplate is required ...
<ListBox Grid.Row="1" x:Name="DailyWeatherListBox"/>
... because once you set the source to a List<DailyWeather>, (or do a binding like Siim Haas's answer suggests) your program will find the DataTemplate we defined for a DailyWeather object that we included in the resources.
While reading about the datatemplates I noticed you can choose different templates based on the type of data. - However can one also do this for different content of the data?
My modelview provides a list of data, in principle it's just a list of tuples (bound in a custom class to make typing easier) with Tuple<ImageData, AltText>.
The type of the property in the ModelView is:
ReadOnlyObservableCollection<ThumbDispData>
With ThumbDispData:
public class ThumbDispData
{
public ImageData Idhl { get; set; }
public string AltText { get; set; }
}
Now I wish to display an Image if it can (ImageData.Source is non null) - Otherwise it should show an alt-text.
The xaml or the usercontrol:
<UserControl x:Class="test.ThumbPanel"
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:test="clr-namespace:test"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate DataType="{x:Type test:ThumbDispData}">
<TextBlock Text="{Binding AltText}"></TextBlock>
</DataTemplate>
</UserControl.Resources>
<Grid Background="Transparent">
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
Well above only shows the alt-text (which works): how would I create a selector based on the content of ThumbDispData.
You could use a DataTemplateSelector:
<UserControl.Resources>
<DataTemplate x:Key="tt" DataType="{x:Type test:ThumbDispData}">
<TextBlock Text="{Binding AltText}"></TextBlock>
</DataTemplate>
<DataTemplate x:Key="img" DataType="{x:Type test:ThumbDispData}">
<Image Source="{Binding Idhl}" />
</DataTemplate>
<local:Selector x:Key="selector" TextTemplate="{StaticResource tt}" ImageTemplate="{StaticResource img}" />
</UserControl.Resources>
<Grid Background="Transparent">
<ItemsControl ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource selector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
public class Selector : DataTemplateSelector
{
public DataTemplate ImageTemplate { get; set; }
public DataTemplate TextTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
ThumbDispData data = item as ThumbDispData;
if (data != null && data.Idhl != null)
return ImageTemplate;
return TextTemplate;
}
}
would I create a selector based on the content of ThumbDispData.
You would typically write a subclass of DataTemplateSelector, in which you check the Idhl property and return a different template object depending on whether it's null or not. Typically, the template would be a resource, which you load dynamically in the selector.
But your scenario is simple enough that I would use styles instead. For example:
<DataTemplate DataType="{x:Type test:ThumbDispData}"
xmlns:p="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Grid>
<TextBlock Text="{Binding AltText}">
<TextBlock.Style>
<p:Style TargetType="TextBlock">
<Setter Visibility="Visible"/>
<p:Style.Triggers>
<DataTrigger Binding="{Binding Idhl}" Value="{x:Null}">
<Setter Visibility="Collapsed"/>
</DataTrigger>
</p:Style>
</p:Style>
</TextBlock.Style>
</TextBlock>
<Image Source="{Binding Idhl}">
<Image.Style>
<p:Style TargetType="Image">
<Setter Visibility="Collapsed"/>
<p:Style.Triggers>
<DataTrigger Binding="{Binding Idhl}" Value="{x:Null}">
<Setter Visibility="Visible"/>
</DataTrigger>
</p:Style>
</p:Style>
</Image.Style>
</Image>
</Grid>
</DataTemplate>
Notes:
Strictly speaking, you don't need the <Setter Visibility="Visible"/> in the TextBlock style, since that's the default value. I put it there because it helps document the symmetry between the styles of the two elements.
The xmlns:p declaration is there only as a Stack Overflow work-around, because the XML formatter doesn't handle the XAML Style element correctly otherwise. In real XAML, you can leave that out and just use the Style name directly instead of p:Style.
so i had the same problem as this person.. WPF: Bind Collection with Collection to a ListBox with groups
I tried to implement the suggested answer to my problem but found the error "The property 'Content' is set more than once". The only difference is id like to implement this in a User Control like so:
here is my c# code:
public class Dog
{
public int dogID { get; set; }
public String Name { get; set; }
List<Puppy> puppies { get; set; }
}
public class Puppy
{
public String Name { get; set; }
}
and this is XAML:
<UserControl x:Class="PetStore.Menu"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<CollectionViewSource x:Name="dogs" Source="{Binding}" >
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Name" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="dogTemplate" DataType="Project">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<ListBox ItemsSource="{Binding Source={StaticResource dogs}}">
<ListBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource dogTemplate}" />
</ListBox.GroupStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="Puppy">
<ListBox ItemsSource="{Binding puppies}">
<ListBox.ItemTemplate>
<DataTemplate DataType="Puppy">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Any help is greatly appreciated!!
You're missing the UserControl.Resources tag:
<UserControl x:Class="Tackle.View.Menu"
...>
<UserControl.Resources> <!-- You're missing this -->
<CollectionViewSource x:Key="dogs" Source="{Binding}" > <!-- Change x:Name to x:Key here -->
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Name" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="dogTemplate" DataType="Project">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<!-- here goes your window content -->
The error message tells you what you did wrong: you attempted to set the Content property more than once. When you declare an element as a direct child of a control, then you are implicitly setting whichever property is specified in the [ContentProperty("...")] attribute on that control type. For ContentControl (and its subclass UserControl), that property is Content. If you want to set a different property, or add something to a collection/dictionary property, you must specify the property name. This means either using the Property="value" attribute syntax or the <Owner.Property> element syntax.
Basically I am trying to do something like this http://www.crunchgear.com/wp-content/uploads/2010/07/popbox.jpg.
Just showing images in a fixed dimension with no text below the images. It also should have a fixed distance between the images horizontally and vertically.
Also if someone could add a mouseover glow effect to simulate the same thing, that would be cool.
I am more familiar with xaml now but I just got confused about defining each item for a ListView control. I only used WPF ListView with GridViewColumns before so this is new.
Also the object I am trying to bind the ListView that has the images is something like this:
class Film
{
Image Image {get;set;}
}
But if I should store them in a different type other than Image, that's cool too.
Here's a quick and dirty example that gives an effect similar to what you want:
XAML
<Window x:Class="WpfListViewDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:WpfListViewDemo"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListView ItemsSource="{Binding Films}"
Background="Black">
<!-- Panel that will contains the items -->
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"
ItemWidth="150" ItemHeight="220"
Width="{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType=ScrollContentPresenter}}"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<!-- Template for each item -->
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type my:Film}">
<Grid>
<!-- Halo that will be shown when the mouse is over the image -->
<Ellipse Name="mouseOverHalo" Visibility="Hidden"
Width="180" Height="250"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ClipToBounds="False">
<Ellipse.Fill>
<RadialGradientBrush Center="0.5, 0.5">
<GradientStop Offset="0.0" Color="Blue" />
<GradientStop Offset="0.8" Color="Blue" />
<GradientStop Offset="1.0" Color="Black" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Image Name="img"
Source="{Binding ImagePath}"
ToolTip="{Binding Title}"
Margin="10" />
</Grid>
<DataTemplate.Triggers>
<!-- Trigger to display the halo when the mouse is over the image -->
<Trigger SourceName="img" Property="IsMouseOver" Value="True">
<Setter TargetName="mouseOverHalo"
Property="Visibility"
Value="Visible">
</Setter>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
Film class
public class Film
{
public string Title { get; set; }
public string ImagePath { get; set; }
}
Code-behind
(You could also use a ViewModel class as the DataContext, I used code-behind here for the sake of simplicity)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Films = new ObservableCollection<Film>(LoadFilms());
this.DataContext = this;
}
private static IEnumerable<Film> LoadFilms()
{
string imagesDirectory = #"D:\Docs\DVD\covers";
return
from file in Directory.EnumerateFiles(imagesDirectory)
select new Film
{
Title = Path.GetFileNameWithoutExtension(file),
ImagePath = file
};
}
public ObservableCollection<Film> Films { get; private set; }
}
The halo is quite ugly right now, but hey, I'm not a designer ;)
I would use an ItemsControl.
As ItemsPanel I would use a WrapPanel. As ItemTemplate you can provide a DataTemplate binding to the Image of your Film, better provide the Image as ImageSource, then you can use it straight away as source for an Image element.
Define a Style for your Image in which you define a Trigger for the IsMouseOver = true that every FrameworkElement has, whatever eye-candy you want when the mouse is over the image.
Ah, was busy mocking up an example :) I'll post it anyway: I added a drop shadow but you can easily change it to a glow., and the C# part just populates a list of file names from a folder next to the .exe.
XAML:
<Window x:Class="pic_viewer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:pic_viewer"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="item_template" DataType="Item">
<Image Source="{Binding}" Width="64" Height="64"/>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BitmapEffect">
<Setter.Value>
<DropShadowBitmapEffect ShadowDepth="3" Color="Black"/>
</Setter.Value>
</Setter>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
<ObjectDataProvider x:Key="pic_list" ObjectType="{x:Type local:Pic}" MethodName="get_pics"/>
</Window.Resources>
<ListView ScrollViewer.HorizontalScrollBarVisibility="Disabled" x:Name="item_listbox"
ItemsSource="{Binding Source={StaticResource pic_list}}" ItemTemplate="{StaticResource item_template}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Window>
C#:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Documents;
namespace pic_viewer
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class Pic
{
public List<string> get_pics()
{
List<string> p = new List<string>();
p.Add(#"pack://siteoforigin:,,,/Images/black.png");
p.Add(#"pack://siteoforigin:,,,/Images/blu.png");
p.Add(#"pack://siteoforigin:,,,/Images/empty.png");
p.Add(#"pack://siteoforigin:,,,/Images/red.png");
return p;
}
}
}