Custom Objects in Custom List WPF - wpf

I have a viewmodel called Calendar.
in Calendar there is a list of CalendarDaySquare objects. They are displayed in a ItemsControl that is a uniformgrid (from the ItemsPanelTemplate).
On each of these calendarDaySquares I want to populate it with the Events Collection (of CalEvent objects)
public Class Calendar
{
// this is just a list that inherits from List<T>
public CalendarSquareList<CalendarDaySquare> Squares { get; private set; }
}
public class CalendarDaySquare
{
public List<CalEvent> Events {get; private set;}
public ObservableCollection<string> ObsColEvents{get; private set;}
}
Are the same thing, I just tried using the ObservableCollection b/c I've been stumped on this.
Here is my xaml :
<ItemsControl ItemsSource="{Binding Squares}"
Margin="0,0,0,263">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="7" Rows="5"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Margin" Value="1"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding}" Margin="5">
<ListBox.ItemTemplate>
<DataTemplate xmlns:local="clr-namespace:BudgetCalendarWPF.ViewModel;assembly=BudgetCalendarWPF" DataType="CalendarDaySquare">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!--</Button>
</ControlTemplate>
</Button.Template>
</Button>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I hope i've pasted the latest (i've been working on this all day so this is just what i've got in VS currently .. sad face )

Welp, I think i've got this kinda figured out but I'd love any suggestions.
I was trying to hard. I didn't realize it would automatically pick up the item as the type it was. so this worked :
<ListBox ItemsSource="{Binding Events}">
<ListBox.ItemTemplate>
<DataTemplate >
<Button HorizontalAlignment="Stretch">
<TextBlock Text="{Binding Name}"/>
</Button>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Related

Is it possible to have a DataTemplate around another DataTemplate?

Hello everyone and welcome to yet another nested DataTemplate question!
In this one, I'd like to have a DataTemplate like this, written on a ResourceDictionary:
<DataTemplate x:Key="Vector3Template">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="X" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding X}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="Y" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding Y}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<xctk:DoubleUpDown Tag="Z" Style="{StaticResource DoubleUpDownStyle}" Value="{Binding Z}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
Being surrounded by a DataTemplate with a border, like the following, also written on a ResourceDictionary (in the future it's gonna have a couple more elements to it):
<DataTemplate x:Key="ComponentTemplate">
<Border Margin="5" BorderThickness="2" BorderBrush="Gray"/>
</DataTemplate>
Why would I want this, you ask? Well, I'm trying to display an ObservableCollection of IComponent named _components and I want all instances to share the same Borders, but with its core being specific to every class type that inherits from IComponent.
In order to display the list with its differents type, I'm using the following code on a UserControl:
<Grid x:Name="LayoutRoot" Background="White">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel>
<ListView x:Name="_componentsList"
ItemsSource="{Binding Components}"
HorizontalContentAlignment="Stretch">
<ListView.Resources>
<DataTemplate DataType="{x:Type models:Transform}">
<ContentControl Content="{StaticResource ComponentTemplate}" ContentTemplate="{StaticResource TransformTemplate}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type models:Vector3}">
<ContentPresenter ContentTemplate="{StaticResource Vector3Template}"/>
</DataTemplate>
</ListView.Resources>
</ListView>
</StackPanel>
</ScrollViewer>
Trying to build this system with Prism 6.3 and with almost no code-behind, every c# code that I have is just for models, so no real logic here so far.
Is this possible? How so? I've started playing with WPF a few days ago and still have a lot to learn.
I believe what you're looking for is to simply use a DataTemplateSelector in which the DataTemplate used is determined by the data. You can find a full tutorial here. Once you've setup your DataTemplateSelector you simply would pass it in as the DataTemplate to your control.
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultnDataTemplate { get; set; }
public DataTemplate BooleanDataTemplate { get; set; }
public DataTemplate EnumDataTemplate { get; set; }
public override DataTemplate SelectTemplate(object item,
DependencyObject container)
{
DependencyPropertyInfo dpi = item as DependencyPropertyInfo;
if (dpi.PropertyType == typeof(bool))
{
return BooleanDataTemplate;
}
if (dpi.PropertyType.IsEnum)
{
return EnumDataTemplate;
}
return DefaultnDataTemplate;
}
}

Bind Usercontrol contained in a ObservableCollection<CustomClass> in a WrapPanel

I have a class defined this way:
public class CustomClass
{
string Name;
int Age;
Usercontrol usercontrol;
}
where Usercontrol is a visual element that I want to insert in a WrapPanel.
CustomClass is organized in a static ObservableCollection.
public static class CollectionClass
{
public static ObservableCollection<CustomClass> MyCollection = new ObservableCollection<CustomClass>();
}
I am looking to bind the usercontrol property of the CustomClass in the collection to be visualized in the WrapPanel, so I have the visual elements showed in the same order as the elements in the collection.
Right now I am populating the WrapPanel manually by code, but I figured that there has to be a way to do it quickly and easily through databinding.
I am trying to do it with a ItemsControl defined this way:
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
but I don't know how to make the magic happen.
EDIT:
I tried the solution proposed by ChrisO, implemented like that:
1 - I made the collection a property
2 - I made the UserControl a property
3 - I set DataContex from code:
SensorMenuWrap.Datacontext = CollectionClass.MyCollection
4 - The binding:
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding usercontrol}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now I can visualize the first element of the collection. If the first element changes, I visualize the new first element. How can I visualize the entire collection?
I haven't tested this. Also, make sure that userControl is a property rather than a field. I'm assuming you know how to set the DataContext up correctly to refer to the MyCollection property.
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding userControl}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You should also consider referring directly to the UserControl in the DataTemplate rather than binding to it as a property on the class. That would look like this
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding MyCollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Edit:
In response to your edit, you would be much better tackling this with a reusable UserControl with DataBindings on the properties of CustomClass. Here's what I have to achieve that:
CollectionClass
public static class CollectionClass
{
public static ObservableCollection<CustomClass> MyCollection { get; set; }
static CollectionClass()
{
MyCollection = new ObservableCollection<CustomClass>();
MyCollection.Add(new CustomClass { Age = 25, Name = "Hamma"});
MyCollection.Add(new CustomClass { Age = 32, Name = "ChrisO"});
}
}
CustomClass
public class CustomClass
{
public string Name { get; set; }
public int Age { get; set; }
}
UserControl contents
<StackPanel>
<TextBlock Background="Tan" Text="{Binding Name}" />
<TextBlock Background="Tan" Text="{Binding Age}" />
</StackPanel>
MainWindow
<ItemsControl Name="SensorMenuWrap" ItemsSource="{Binding }">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<so25728310:Usercontrol Margin="5" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here, instead of having the Usercontrol baked into your data, you're keeping it in the view. It's always good to have a separation between your view and data. You could even do away with the Usercontrol and have the two TextBoxes directly in the DataTemplate but it depends on whether you would want to reuse that view elsewhere.

ListBox Grouping issue

I am trying to Group my collection which is based on my following model:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Role PersonRole { get; set; }
}
public class Role
{
public int Id { get; set; }
public string RoleName { get; set; }
}
My PersonCollection,
public ObservableCollection<Person> PersonList;
The collection is populated with three Person object, two with identical roles.
I want to group all my persons as per their RoleName. I have the following XAML:
<Grid.Resources>
<CollectionViewSource x:Key="cvs" Source="{Binding PersonList}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="PersonRole.RoleName"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Grid.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource cvs}}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FirstName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding PersonRole.RoleName}" FontWeight="Bold" Background="ForestGreen" Margin="0,5,0,0"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
</ListBox>
However the rendered UI is not displaying the grouped RoleName in the list box. Also I would like to show the grouping horizontally where the groups appear horizontally while the grouped items appear vertically. Appreciate any help in advance.
Here's the output I am seeing:
Since you have already provided GroupDescriptons at your CollectionViewSource, groupStyle will pick the property name from there only.
All you need is to bind with Name property of CollectionViewGroup class to access the value of the property on which grouping is applied. In group style, binding is against CollectionViewGroup class and not against your Model class.
So, this is how you will do it:
<TextBlock Text="{Binding Name}" FontWeight="Bold"
Background="ForestGreen" Margin="0,5,0,0"/>
And regarding to align groups horizontally, you can set ItemsPanelTemplate of Group to be VirtualizingStackPanel with Orientation set to Horizontal which makes your groups to be aligned horizontally.
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontWeight="Bold"
Background="ForestGreen" Margin="0,5,0,0"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>

Silverlight: binding a button command within a DataGrid header within an ItemsControl

I have a bit of a problem binding a button command to a property of an "outer" data context.
The image below shows the layout I have. I'm trying to bind the CommandParameter of the "Clear" button (outlined in red) to the LocationId. The ItemsControl repeats an ObservableCollection of Locations (see below for Location definition).
The Clear button is basically trying to REMOVE the Addresses attached to the Location, to clear down the DataGrid. To do that I need to pass the LocationId to the ViewModel.
The actual command is triggered perfectly but the binding on the CommandParameter isn't quite right.
Here are the underlying classes of Location & Address:
class Location
{
int Id;
ObservableCollection<Address> Addresses;
}
class Address
{
string AddressText;
}
And here is the XAML commented with three alternative attempts & error messages:
<ItemsControl ItemsSource="{Binding Locations, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel VerticalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}"/>
<sdk:DataGrid x:Name="ResponseDataGrid" ItemsSource="{Binding Addresses}">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="Response" Width="*" Binding="{Binding AddressText}"/>
<sdk:DataGridTemplateColumn Width="100">
<sdk:DataGridTemplateColumn.HeaderStyle>
<Style TargetType="sdk:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Content="Clear"
Command="{Binding DataContext.ClearLocationCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding Id, RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
<!--System.Windows.Data Error: BindingExpression path error: 'Id' property not found on 'System.Windows.Controls.ItemsControl' -->
<!--CommandParameter="{Binding ItemsSource.Id, RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>-->
<!--System.Windows.Data Error: BindingExpression path error: 'Id' property not found on 'System.Collections.ObjectModel.ObservableCollection`1[UI.Location]' -->
<!--This gives me the ID but uses a specific index so only works for the first repeated Location-->
<!--CommandParameter="{Binding ItemsSource[0].Id, RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>-->
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</sdk:DataGridTemplateColumn.HeaderStyle>
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Accept">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction
Command="{Binding DataContext.SelectedAddressCommand , RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=sdk:DataGrid}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
From the errors, it seems that the button can't quite see the repeated Location object, it can either see the collection of Locations or a specific indexed Location but not the repeated, generated Location that I NEED!
Thanks!
So you have a ViewModel with an ObservableCollection<Location> And clicking on Clear clears the ObservableCollection<Address> of the specified Location, right?
Why don't you just place the Clear command into the Location class? I dont know about the logic triggered by this command, but doing so you will be able to acces to the correct location Id property.
Edit: here it is my example working:
Classes
public class Location
{
public Location(int id, IEnumerable<Address> addresses)
{
this.Id = id;
this.Addresses =new ObservableCollection<Address>(addresses);
this.ClearLocationCommand = new RelayCommand<int>(e => MessageBox.Show(string.Format("Clear command called on Location {0}", this.Id)));
}
public int Id { get; set; }
public ObservableCollection<Address> Addresses { get; set; }
public ICommand ClearLocationCommand { get; set; }
}
public class Address
{
public Address(string text)
{
this.AddressText = text;
}
public string AddressText { get; set; }
}
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MainWindowViewModel()
{
Locations = new ObservableCollection<Location>(new []
{
new Location(1, new [] { new Address("A1") }),
new Location(2, new [] { new Address("A2"), new Address("A3"), }),
});
}
public ObservableCollection<Location> Locations { get; set; }
private void OnPropertyChanged(string porpertyName)
{
var e = this.PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedEventArgs(porpertyName));
}
}
}
XAML
<Window x:Class="TestBindingButtons.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:TestBindingButtons="clr-namespace:TestBindingButtons" Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<TestBindingButtons:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Locations, Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel VerticalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Id}"/>
<DataGrid x:Name="ResponseDataGrid" ItemsSource="{Binding Addresses}">
<DataGrid.Columns>
<DataGridTextColumn Header="Response" Width="*" Binding="{Binding AddressText}"/>
<DataGridTemplateColumn Width="100">
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Button Content="Clear"
Command="{Binding DataContext.ClearLocationCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Accept">
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
PD:
I didn't posted the implementation of RelayCommand, use your own.
I didn't used your "sdk" framework, just plain microsoft stuff.
If you move the ClearLocationCommand property to the Location class, remember to change the following bindings:
<Button Content="Clear"
Command="{Binding DataContext.ClearLocationCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
CommandParameter="{Binding Id, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
/>
to:
<Button Content="Clear"
Command="{Binding ClearLocationCommand}"
CommandParameter="{Binding Id}"
/>
Because each outer Location object now has its own clear command, and you are already on the correct DataContext in that item row.

WPF combox and multiple checkboxes

In XAML how would you have in a list or grid on the left side a combo box and the right side multiple check boxes in a straight line?
Let say I had a data structure like.
sudo:
// for combo
class Option
{
int key {get;set;}
string value{get;set;}
}
// for checkboxes
class Selection
{
int key {get;set;}
string value{get;set;}
bool isSelected {get;set;}
}
class Item
{
Item
{
selections = new List<Selection>();
Options = new List<Option>();
}
List<Selection> selections {get;set;}
List<Option> Options{get;set;}
}
Now this would be the item source.
List<Item> x = new List<Item>();
Item i = new Item();
i.Selections.add(blah); 25 selections
i.Options.add(blah); 3 checkboxes
x.add(i) 50 combination's.
control.itemsource = x;
What would the XAML look like. I am stuck as I quite dont get it.
Thanks...
<ListBox ItemsSource="{Binding Items}" >
<ListBox.ItemTemplate>
<DataTemplate>
<!-- This is your combobox -->
<DockPanel HorizontalAlignment="Stretch" LastChildFill="False">
<ComboBox ItemsSource="{Binding Options}" DockPanel.Dock="Left">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding value}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- This is your line of checkboxes -->
<ListBox ItemsSource="{Binding Selections}" DockPanel.Dock="Right">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding isSelected}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Resources