WPF: Can I have multiple ItemsControls with different ItemsSources? - wpf

In my WPF App, I want to have several items that are generated by the user on runtime. These are from different classes and hence, my original idea was to add them to different Observable Collections and then use them as ItemsSources od different ItemsControls. However, WPF gives me the error System.InvalidOperationException: Items collection must be empty before using ItemsSource. I'm not a WPF expert, but the answer to THIS SO question seems to indicate that I can only have 1 ItemsControl.
THIS SO question indicates that maybe I should use the CompositeCollection Class, but unlike in the cited question, I have several completely different Observable Collections for completely different tasks.
Here is the relevant part of my XAML.CS with two Collections: 1 of a custom Interface type and 1 of custom Class type
public MainWindow()
{
InitializeComponent();
DefaultWindowDefinition.ItemsSource = ProcessElements = new ObservableCollection<IProcessSimulator>();
PathControl.ItemsSource = PathElements = new ObservableCollection<VisualPath>();
}
And here is the relevant part of the XAML I tried to use:
<Grid x:Name="MainGrid"
Background="{StaticResource Alternating}"
MouseLeftButtonUp="grid_MouseLeftButtonUp"
ShowGridLines="True">
<ItemsControl Name="DefaultWindowDefinition"
ItemsSource="{Binding ProcessElements}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--HERE IS A LONG LIST OF ELEMENTS-->
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!--TEMPLATE FOR THE 1ST ITEMSCONTROL-->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<!--STYLE PROPERTIES FOR THE 1ST ITEMSCONTROL-->
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
<ItemsControl Name="PathControl"
ItemsSource="{Binding PathElements}">
<DataTemplate>
<!--HERE IS A LIST OF OTHER TYPE OFELEMENTS-->
</DataTemplate>
</ItemsControl>
</Grid>
How should I approach this problem or rather, what C#/WPF element should I use? A reference and some words of simple explanation is more than enough, I can google the rest myself, I just don't know, what to look for really.

You should set the ItemTemplate property of the "PathControl" to your DataTemplate:
<ItemsControl Name="PathControl" ItemsSource="{Binding PathElements}">
<ItemsControl.ItemTemplate> <!-- NOTE THIS -->
<DataTemplate>
<!--HERE IS A LIST OF OTHER TYPE OFELEMENTS-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
If you omit the <ItemsControl.ItemTemplate> element, you are adding a DataTemplate to the Items collection of the ItemsControl and you can't also set its ItemsSource property.
Trying to do so will results in an System.InvalidOperationException exception being thrown with the error message you are getting.
It is perfectly fine for several ItemsControl to bind to the same source collection.

It seems like you are setting the ItemsSource twice. Once in the code behind and once in the XAML. Remove the code behind that sets the Items source and just initialize the observable collections. The XAML should take care of binding to the collections.

Related

Programmatically Adding Buttons in MVVM but Command doesn't work

I'm trying to add buttons dynamically and it's work but command doesn't work.
I'm getting error
ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='RadRibbonButton'
<telerik:RadRibbonGroup>
...
<telerik:RadRibbonGroup.DataContext>
<vm:Group1/>
</telerik:RadRibbonGroup.DataContext>
<ItemsControl ItemsSource="{Binding ButtonsCollection}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" Command="{Binding DataContext.ButtonCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type telerik:RadRibbonGroup}}}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</telerik:RadRibbonGroup>
ButtonCommand= new DelegateCommand(ExecuteCommand);
private void ExecuteCommand()
{
...
}
Check that it does reach the command in debug mode.
Is your ButtonsCollection is dependency property? do you raise its change?
You are not supposed to add actual buttons to your buttons collection, thats is not pure mvvm. you need to create the buttons in a data template, and in the collection you need to create the properties like the button's content etc.
If you still insist creating actual buttons, i think that templating "" won't work. Try "
It doesn't reach
ObservableCollection of buttons(and I can see the buttons in the view)
3.I should create and add buttons to collection runtime(There are special logic behind this).In constructor I create command

wpf grid with dynamic columns

I have a collection that I wish to bind to a WPF grid.
The problem I'm facing is that the number of columns is dynamic and is dependent on a collection. Here is a simple mock up:
public interface IRows
{
string Message{get;}
IColumns[] Columns{get;}
}
public interface IColumns
{
string Header {get;}
AcknowledgementState AcknowledgementState{get;}
}
public interface IViewModel
{
ObservableCollection<IRows> Rows {get;}
}
I want my view to bind to the the Rows collection, which contains a collection of Columns.
My Columns collection contains an enum which should be represented by an image (1 of 3 possibilities). It also contains a Message property which should only be displayed in one column (static and is just some text information). It also contains a Header string which should be displayed as a header for that column.
Note that the number of columns is variable (at the moment the headers are set to Acknowledge but this will change to represent dynamic data).
Update: This is after implementing suggestions from Rachel
<ItemsControl
ItemsSource="{Binding Items, Converter={StaticResource PresentationConverter}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid ShowGridLines="true"
local:GridHelpers.RowCount="{Binding RowCount}"
local:GridHelpers.ColumnCount="{Binding ColumnCount}" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Row" Value="{Binding RowIndex}"/>
<Setter Property="Grid.Column" Value="{Binding ColumnIndex}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type UI:MessageEntity}">
<TextBox Text="{Binding Message}"></TextBox>
</DataTemplate>
<DataTemplate DataType="{x:Type UI:StateEntity}">
<TextBox Text="{Binding State}"></TextBox>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This almost gives me what I want now. I'm only stuck with what I should do for the headers.
Any suggestions are welcome.
You can use nested ItemsControls for this
Here's a basic example:
<!-- Bind Rows using the default StackPanel for the ItemsPanel -->
<ItemsControl ItemsSource="{Binding Rows}">
<!-- Set the Template for each row to a TextBlock and another ItemsControl -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- Need to set Width of name TextBlock so items line up correctly -->
<TextBlock Width="200" Text="{Binding Name}" />
<ItemsControl ItemsSource="{Binding Columns}">
<!-- Use a horizontal StackPanel to display columns -->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Using a grid approach might make things more complicated than they should be. Have you tried changing the template of a listview, or to use the DataGrid instead for this purpose?
For an example, take a look at this project: http://www.codeproject.com/Articles/25058/ListView-Layout-Manager
Or this one: http://www.codeproject.com/Articles/16009/A-Much-Easier-to-Use-ListView
If you go with the Grid, I believe you'll have to add a lot of code behind to manage the amount of columns and rows, their size, the cell content... Whereas a ListView/DataGrid will let you do this dynamically through Templates.
Create a Grid using code as shown at http://msdn.microsoft.com/en-us/library/system.windows.controls.grid(v=vs.90).aspx#feedback
create a property of type ColumnDefinition,( and use property changed ) to create columns.
There is also the option of using a dynamic object to create your columns. This is a bit laborious but the results are very effective and the solution in general is quite flexible.
This will show you the basics to the dynamic object
Binding DynamicObject to a DataGrid with automatic column generation?
I had some trouble using it with nested objects, columns that have objects and then trying to bind cell content to the object.
Here's a question I raised with an example of how to do this
Problems binding to a the content of a WPF DataGridCell in XAML

showing different user controls - WPF MVVM

I created a dbml file, which automatically created the designer.cs
In the designer.cs (which is the Model in the MVVM) there are two different classes in the database:
ElementA and ElementB.
I have two user controls:
Element_A_UserControl - displays a single instance of ElementA
Element_B_UserControl - displays a single instance of ElementB
There is another user control that has two stack panels.
The first stack panel displays a list of Element_A_UserControl
The second stack panel displays a list of Element_B_UserControl
Here is the stack panel #1 XAML:
<StackPanel>
<ItemsControl ItemsSource="{Binding AllElements_A}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<vw:Element_A_UserControl x:Name="elementA">
</vw:Element_A_UserControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Here is the stack panel #2 XAML:
<StackPanel>
<ItemsControl ItemsSource="{Binding AllElements_B}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<vw:Element_B_UserControl x:Name="elementB">
</vw:Element_B_UserControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Up to now everything works fine.
I want to have one stack panel, which displays a list of ElementA or a list of ElementB depending of a condition.
Note: The property to get list of elements is different.
i.e.
ItemsSource="{Binding AllElements_A}
ItemsSource="{Binding AllElements_B}
I hope that my question is clear enough.
Dazy.
One way is that you could try to use a conditional DataTemplate. Something like this:
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:ElementAType}">
<vw:Element_A_UserControl x:Name="elementA">
</vw:Element_A_UserControl>
</DataTemplate>
<DataTemplate DataType="{x:Type local:ElementBType}">
<vw:Element_B_UserControl x:Name="elementB">
</vw:Element_B_UserControl>
</DataTemplate>
</ItemsControl.Resources>
Then, in your viewmodel, create:
public ObservableCollection<object> CombinedCollection {get; set;}
and load it with either of your collections conditionally.
Alternatively, keep both ItemsControls in your XAML, and conditionally hide/show them using Visibility and a BooleanToVisibilityConverter. Given these two choices, I would likely choose this one, as it is clearer in the code, and easier to maintain than the conditional DataTemplate above. However, you seemed to indicate that you didn't want to do that, so I presented the first one as an option.

Error when Databinding with mode=twoway

All,
I'm pounding my head against the wall here. What I need is simple, and I'm sure there is a simple answer, but I can't seem to find it.
Situation: I have a Silver light 4.0 app and I'm currently binding a list of strings to an Items control. In the data template for that I have a simple text box that I was doing very basic Binding "{Binding}". I need to update the binding to be twoway so the edits are automatically pushed back to my datacontext.
Here is the Items control before I update the binding:
<ItemsControl x:Name="spLiftHeader" ItemsSource="{Binding Path=WeekLabels}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel x:Name="spLiftHeader" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
**<TextBox x:Name="txtWeekLbl" Text="{Binding}" Foreground="Black" Width="125" TextAlignment="Center"/>**
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the items control after the binding change:
<ItemsControl x:Name="spLiftHeader" ItemsSource="{Binding Path=WeekLabels}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel x:Name="spLiftHeader" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
**<TextBox x:Name="txtWeekLbl" Text="{Binding Mode=TwoWay}" Foreground="Black" Width="125" TextAlignment="Center"/>**
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I've just simply added the "Mode=TwoWay" to the binding.
After updating that, I get the amazingly useless error 4004
"System.Windows.Markup.XamlParseException: Provide value on 'System.Windows.Data.Binding' threw an exception" and the Line/Position reference points right do my updated Binding.
How does one add the Two Way mode to the simple binding?
Thanks in advance.
Nick
Two-way binding to an entire object (a string in this case) makes no sense to Silverlight so it is correct to throw an exception. Shame it is not a more useful error message :)
When there is no Path in the binding the ItemsControl can fetch a value using Object.ToString(), but where will it store the result back? It can't replace the string as that would require placing a new string object back in the collection. Two-way binding is done via reflection against a property of an object.
Instead of a simple list of strings, use a list of some new object that contains a string property and explicitly bind to that property. It will make everything a lot easier. (Make sure your new class and property implement INotifyPropertyChanged).
I am not 100% sure, but I don't think the the Mode=TwoWay should be set in the TextBox itself.
If that doesn't work, try it on both. However, in either case do not use List<T> as a data source behind that items control. Lists do not fire changed event when one of its items has changed. Use ObservableCollection<T> (from the System.Collections.ObjectModel namespace) instead of the List<T>

StackPanel not updating

I am having an issue with a directly bound ObservableCollection not updating a StackPanel when new items are added. Any initial items show up correctly. Only items that are added later on fail to display.
XAML:
<ItemsControl x:Name="ImageTable" ItemsSource="{Binding Path=SystemObjectViewItems, Converter={StaticResource UIElementWrapper}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<ContentPresenter Content="{Binding Path=Value.View}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I am using Prism MVVM so I am binding to my ViewModel which has a property:
public ObservableCollection<SystemObjectViewPresentationModel> SystemObjectViewItems {get; set; }
The basic converter and binding are working as can be demonstrated by the fact that my initial item displays correctly. It’s only items that are added to the collection after initial binding that do not show up.
Any ideas?
Thanks,
Rick
I'm going to take a wild guess and say it's the StaticResource you're using.
If you're not returning an ObservableCollection from it and you're not binding it to watch the original ObservableCollection changes it won't work.
Can you post the code to the converter?

Resources