Given a list of objects containing two properties (IdentityType and Name) in the format:
IdentityType | Name
A | One
A | Two
A | Three
B | Four
B | Five
C | Six
Is there a way to declaratively databind that so the accordion displays like this?
A
- One
- Two
- Three
B
- Four
- Five
C
- Six
So far the best I can get is a panel header for each item, like so:
<toolkit:Accordion ItemsSource="{Binding Path=Identities}" Grid.Row="2" SelectionMode="ZeroOrMore">
<toolkit:Accordion.ItemTemplate>
<DataTemplate >
<TextBlock Text="{Binding IdentityType, Converter={StaticResource EnumDescriptionConverter}}"/>
</DataTemplate>
</toolkit:Accordion.ItemTemplate>
<toolkit:Accordion.ContentTemplate>
<DataTemplate>
<StackPanel Margin="5" Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Foreground="White" />
</StackPanel>
</DataTemplate>
</toolkit:Accordion.ContentTemplate>
</toolkit:Accordion>
I'm new to Silverlight so I could be missing something blindingly obvious, but any help would be very much appreciated!
You can do this with a view model inbetween your model (the initail list) and your view (the markup).
Create a view model class with a Title and a NameCollection
Use LINQ (or a simple foreach) to translate your existing list of 6 entities to a list of 3 entites with 3, 2 and 1 Names in their name collection respectively.
Bind your Accordions ItemsSource to the collection of ViewModel objects.
Bind the text block in the your accordion items header template to your Title property
Add a repeating item control like ItemsControl to your content template of your accordion item
Bind your repeating item to the NamesCollection
Assuming your model is as follows...
public class Model
{
public string Title { get; set; }
public string Name { get; set; }
}
Your View Model structure should be...
public class ViewModel
{
public string Title { get; set; }
public List<string> Names { get; set; }
}
public class DataContextClass
{
public DataContextClass()
{
var modelData = new ModelData();
var query = from m in modelData.ModelCollection
group m by m.Title
into vm select new ViewModel { Title = vm.Key, Names = vm.Select(x => x.Name).ToList() };
ViewModelCollection = query.ToList();
}
public List<ViewModel> ViewModelCollection { get; set; }
}
Then your view can create an instance of your DataContextClass, assign it to it's own DataContext property and then use this markup...
<layout:Accordion ItemsSource="{Binding Path=ViewModelDataInstance.ViewModelCollection}" >
<layout:Accordion.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</layout:Accordion.ItemTemplate>
<layout:Accordion.ContentTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=Names}" />
</DataTemplate>
</layout:Accordion.ContentTemplate>
</layout:Accordion>
You can also use Tuple instead.
Code becomes :
public class DataContextClass{
public DataContextClass()
{
var modelData = new ModelData();
var query = from m in modelData.ModelCollection
group m by m.Title
into vm select Tuple.Create(vm.Key, vm.Select(x => x.Name).ToList() };
Collection = query.ToList();
}
public Tuple<string,List<string>> Collection { get; set; }
}
Xaml become :
<layout:Accordion ItemsSource="{Binding Path=ViewModelDataInstance.ViewModelCollection}" >
<layout:Accordion.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Item1}" />
</DataTemplate>
</layout:Accordion.ItemTemplate>
<layout:Accordion.ContentTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=Item2}" />
</DataTemplate>
</layout:Accordion.ContentTemplate>
I hope it helps
Related
I have a WPF DataGrid bound to a DataTable. The DataTable has columns containing strings and columns containing custom objects (Requirement):
public class PdfFormData
{
public string SupplierName { get; set; }
public List<Requirement> Requirements = new List<Requirement>();
public class Requirement
{
public string ID { get; set; }
public string SupplierStatus { get; set; }
public string SupplierComment { get; set; }
public string OEMStatus { get; set; }
}
}
I'm adding the columns to the DataGrid with this loop:
PHDataGrid.ItemsSource = dataSet.Tables["ReqIfTable"].DefaultView;
PHDataGrid.Columns.Clear();
foreach (DataColumn dataColumn in dataSet.Tables["ReqIfTable"].Columns)
{
if(dataColumn.DataType == typeof(ReqForms.PdfFormData.Requirement))
{
DataGridTemplateColumn dgColumn = new DataGridTemplateColumn();
dgColumn.Header = dataColumn.ColumnName;
dgColumn.CellTemplate = (DataTemplate)FindResource("dgTemplateRequirement");
dgColumn.CanUserSort = true;
dgColumn.IsReadOnly = true;
PHDataGrid.Columns.Add(dgColumn);
}
else
{
DataGridTextColumn dgColumn = new DataGridTextColumn();
dgColumn.Header = dataColumn.ColumnName;
Binding dgBinding = new Binding(dataColumn.ColumnName);
dgColumn.Binding = dgBinding;
dgColumn.CanUserSort = true;
dgColumn.IsReadOnly = true;
PHDataGrid.Columns.Add(dgColumn);
}
}
And I have defined a template for showing my Requirement objects:
<Window.Resources>
<DataTemplate x:Key="dgTemplateRequirement">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Requirement.SupplierStatus}" />
<TextBlock Text="{Binding Requirement.SupplierComment}" />
<TextBlock Text="{Binding Requirement.OEMStatus}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
The Binding to the Requirement properties (SupplierStatus, SupplierComment, ...) is not working and stays empty. How can I bind to the properties of my custom object that is stored in the DataTable?
The bindings do not work, because the DataContext of the the templated cell is a DataRowView.
You can either create a converter as in this related post or just access the members directly with an indexer binding path, where requirement is the name of your column in the DataTable, e.g.:
<TextBlock Text="{Binding Row[requirement].SupplierStatus}" />
I set the DataContext of the StackPanel to the Requirement object, so it is even easier to bind.
<DataTemplate x:Key="dgTemplateRequirement">
<StackPanel Orientation="Vertical" DataContext="{Binding Row[requirement]}">
<TextBlock Text="{Binding SupplierStatus}" />
<TextBlock Text="{Binding SupplierComment}" />
<TextBlock Text="{Binding OEMStatus}" />
</StackPanel>
</DataTemplate>
I found the solution in this post:
WPF DataGrid - Databind to DataTable cell in CellTemplates DataTemplate
You have to use a converter to reach from the table to the actual object.
I have an object(MyCals) that is type of Cal Collection. (Cal is an object).
The MyCals consists of 2 Cal, where each Cal has List of Events (Events does not has the INotifyPropertyChanged interface implemented, it has many properties like Summary, Id,...)
<TabControl ItemsSource="{Binding Path=MyCals, UpdateSourceTrigger=PropertyChanged}">
<TabControl.ItemTemplate>
<DataTemplate>
<!-- Tab Header -->
<TextBlock Text="{Binding Path=ProductID}" />
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Content -->
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<DataGrid AutoGenerateColumns="False"
HorizontalGridLinesBrush="LightGray"
ItemsSource="{Binding Path=Events, UpdateSourceTrigger=PropertyChanged}"
VerticalGridLinesBrush="LightGray">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Summary}" Header="Summary" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Now the TabControl will show 2 tabs with the right header, but the Datagrid wont update when I change select the other tab. So if the First Tab is selected, that datagrid will be showed with the Data from first Cal, but when I select the second tab, data in the datagrid are not getting updated
(Same Events in the MyCals[1])
any help is really appreciated
I don't know how you implemented the ViewModel so I did a quick test and it works.
This is the code of the ViewModel:
class MainViewModel : AbstractViewModel
{
public MainViewModel()
{
this.MyCals = new ObservableCollection<Cal>();
this.MyCals.Add(new Cal());
this.MyCals.Add(new Cal());
}
public ObservableCollection<Cal> MyCals { get; set; }
}
public class Cal
{
public Cal()
{
this.ProductID = "Product " + Guid.NewGuid().ToString();
this.Events = new ObservableCollection<Event>();
this.Events.Add(new Event());
this.Events.Add(new Event());
}
public string ProductID { get; set; }
public ObservableCollection<Event> Events { get; set; }
}
public class Event
{
public Event()
{
this.Summary = "Event Summary " + Guid.NewGuid().ToString();
}
public string Summary { get; set; }
}
Please share a bit more about the code behind if my sample didn't help.
I have the following XAML containing a ListBox bound to an ObservableCollection
<ListBox
Margin="0,5"
Grid.Row="1"
Grid.ColumnSpan="3"
Visibility="{Binding ArePicturesAvailable, Converter={StaticResource BoolToVisConv}}"
SelectedItem="{Binding SelectedPicture}"
ItemsSource="{Binding Pictures, NotifyOnSourceUpdated=True}"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Image Margin="8"
Height="{Binding Size.Height}"
Width="{Binding Size.Width}"
Source="{Binding FullPath, Converter={StaticResource RelativeToFullConv}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Since there is noticeable delay in populating the ListBox, I am attempting to defer this until the ListBox's containing view has loaded using Dispatcher.BeginInvoke as illustrated below:
public class PictureMarkerEditorViewModel
{
private PictureSelectorViewModel _pictureSelectorViewModel;
public string PhotoCollectionDirectory { get; set; }
protected ISymbolEditor GetEditorImpl(ISymbolInfo symbolInfo)
{
var picSymbolInfo = (PictureMarkerSymbolInfo)symbolInfo;
_pictureSelectorViewModel = new PictureSelectorViewModel(picSymbolInfo);
var editor = new PictureMarkerSymbolEditor(_pictureSelectorViewModel, picSymbolInfo);
editor.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
var photos = GetPhotoCollection();
_pictureSelectorViewModel.Pictures.AddRange(photos);
}));
//var photos = GetPhotoCollection();
//_pictureSelectorViewModel.Pictures.AddRange(photos);
return editor;
}
public IEnumerable<Picture> GetPhotoCollection()
{
if (string.IsNullOrEmpty(PhotoCollectionDirectory))
return null;
if (!Directory.Exists(PhotoCollectionDirectory))
throw new ArgumentException("Cannot show images from invalid directory " + PhotoCollectionDirectory + ".");
var files = Directory.GetFiles(PhotoCollectionDirectory, "*.png", SearchOption.AllDirectories);
return files.Select(f => new Picture(Path.GetFileName(f), f, ImageUtils.GetDimensions(f)));
}
}
where PictureSelectorViewModel.Pictures is simply an ObservableCollection of Picture:
public class PictureSelectorViewModel
{
private readonly ObservableCollection<Picture> _pictures= new ObservableCollection<Picture>();
public ObservableCollection<Picture> Pictures
{
get
{
return _pictures;
}
}
}
and Picture is a simple class containing Name and FullPath properties:
public class Picture
{
public string Name { get;set}
public string FullPath {get; set;}
public Size Size {get;set;}
}
I simply cannot get the list populated after it has loaded. If I populate the Pictures collection before the view is loaded (the commented part of the code, I see the images.
Any one know why?
TIA.
I want to bind my Datatemplate to 2 Datasources, one datasource that will actually define what is in the ListBox and other that will determine how many ListBoxes are there and what Items in the Listbox are selected\checked.
I have following XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="TokenListTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chkToken" IsChecked="{Binding Path=IsSelected, Mode=TwoWay}">
<TextBlock Text="{Binding Path=Text}" />
</CheckBox>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ItemTemplate">
<Border BorderThickness="1">
<StackPanel Margin="3">
<TextBlock Text="{Binding Path=Header}"/>
<ListBox ItemTemplate="{StaticResource TokenListTemplate}"
ItemsSource="{Binding Path=Tokens}" >
</ListBox>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemTemplate="{StaticResource ItemTemplate}"
ItemsSource="{Binding}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
And this is the codebehind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<DataEntity> _actualObjects;
List<Token> tokens1 = new List<Token>()
{
new Token("1"),
new Token("2"),
new Token("3"),
new Token("4")
};
List<Token> tokens2 = new List<Token>()
{
new Token("11"),
new Token("21"),
new Token("31")
};
_actualObjects = new ObservableCollection<DataEntity>()
{
new DataEntity(tokens1, "A", "1,2,3", 1),
new DataEntity(tokens1, "B", "2,3", 1),
new DataEntity(tokens2, "C", "21,31", 2)
};
DataContext = _actualObjects;
}
class DataEntity
{
public DataEntity(List<Token> tokens, string header, string tokenString, int entityTypeId)
{
Tokens = tokens;
Header = header;
TokenString = tokenString;
EntityTypeId = entityTypeId;
}
public List<Token> Tokens { get; set; }
public String Header { get; set; }
public String TokenString { get; set; }
public int EntityTypeId { get; set; }
}
public class Token
{
public bool IsSelected { get; set; }
public string Text { get; set; }
public Token(string text)
{
this.IsSelected = false;
this.Text = text;
}
}
}
It produces this
I don't want to inject token1 or token2 List into DataEntity object so in other words I want DataEntity constructor to be
public DataEntity(string header, string tokenString, int entityTypeId)
Listbox DataTemplate should select
tokens1 List as datasource for its LisBoxItems if
Dataentity.EntityTypeId = 1
tokens2 List as datasource for its LisBoxItemsif
DataEntity.EntityTypeId = 2
Also TokenString in DataEntity should be bound to items in the Listbox i.e. if Listbox shows 1 2 3 4
and DataEntity for this listbox has its TokenString value set to "1,2,3" then 1 2 3 should be checked in the listbox
I would recommend to create a ViewModel as a layer between your model and the view. In the ViewModel you can arrange the data to fit to the used controls without changing your model.
So the ViewModel could for example split the tokenString of the DataEntity into a list of tokens.
Just Google for MVVM (Model-View-ViewModel) for examples and furter explanations or look here on SO (like MVVM: Tutorial from start to finish?).
You're not thinking about this correctly. You need to create one class (some may call a view model) with the responsibility of providing all of the data that the view (or UI) will need. Therefore, you will need to have one property which holds a collection of type DataEntity (if I understand you correctly) to 'define what is in the outer ListBox' as you say.
Then you need a DataTemplate to describe what should be displayed for each item in the ListBox - your 'ItemTemplate' template. This DataTemplate should have another ListBox inside in which to display your Token objects. Your DataEntity should have something like this property in it:
public List<Token> Tokens
{
get
{
if (EntityTypeId == 1) return tokens1;
else if (EntityTypeId == 2) return tokens2;
}
}
You will then need another DataTemplate for your Token objects - your 'TokenListTemplate' template, but without the StackPanel... the inner ListBox replaces that, eg. if there are two Token objects in one DataEntity object, then that object would show two Checkboxes... you have correctly bound the IsChecked property to the Token.IsSelected property.
This may be complicated, but it is entirely possible. Just start with the first layer and get your DataEntity objects displayed in the outer ListBox using your 'ItemTemplate' template. Once that bit is ok, move on to the inner ListBox. Good luck.
I have an ItemsControl with a DataTemplate that has been defined. My ItemsControl definition looks like the following:
<ItemsControl x:Name="myItemsControl" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<CheckBox x:Name="myCheckBox" Content="{Binding Name}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is a simplified version of my DataTemplate. Regardless, when a user clicks a button on the page, I want to loop through the items in myItemsControl and determine if the CheckBox element associated with the item is checked.
How do I determine if a CheckBox is checked for a specific item within an ItemsControl?
Thank you!
Add a property to your data class and databind it, then iterate over the collection itself.
public class myDataClass
{
public string Name { get; set;}
public bool IsSomething { get; set; }
}
<CheckBox x:Name="myCheckBox" Content="{Binding Name}" IsChecked="{Binding IsChecked, Mode=TwoWay}" />
You can try something like traditional iteration:
public bool? TestMyCheckbox(string bindingName)
{
foreach (var item in myItemsControl.Items)
{
if (item.GetType() == typeof(CheckBox))
{
var checkbox = (CheckBox)item;
if (checkbox.Content.Equals(bindingName))
{
return (checkbox.IsChecked);
}
}
}
return null;
}
Additionaly (this may better fit your needs) you can look for a list of checkboxes bindings that are checked:
public IEnumerable<object> TestMyCheckboxes(ItemsControl control)
{
return from Control x in control.Items
where x.GetType().Equals(typeof(CheckBox)) && ((CheckBox)x).IsChecked == true
select ((CheckBox)x).Content;
}