Hide last item in a ItemsControl - c#

I've a ItemsControl that I bind to my viewmodel, but inside the datatemplate I also have an image. I want that image to be visible as long as it's not the last item in the list, then it should be hidden (it's an arrow that point down to the next control).
The xaml look like this:
<ItemsControl ItemsSource="{Binding PageContainers}" x:Name="Items">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<controls:DesignControl DataContext="{Binding}" MouseDown="UIElement_OnMouseDown" MouseUp="UIElement_OnMouseUp" MouseMove="UIElement_OnMouseMove"/>
<Image Source="/Resources/Images/arrow.png" Height="16" Width="16" Margin="0,10,0,0"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
So is there any easy to check if the Image/Stackpanel is last in the list? I guess I could subscribe to some event and do it in the code behind, but I guess it's cleaner if I could do it inside the xaml.

You're binding to PageContainers which I assume to be a collection. Can the type of that collection be extended to include an IsLast property?
If it can, you can bind the visibility to that.

Related

How to bind to a source inside a ListBox different from the ItemsSource already specified

I have a ListBox inside a HubSection, whose Items are bound to a class "players" added to my DefaulViewModel via code behind.
First I simply put a TextBox bound to the property "PlayerName" of my class "players".
Now I would like to add a ComboBox with some items that are NOT part of the class players.
Is it possible ? I thought that definind an ItemsSource in the ComboBox would sort of override the ItemsSource of the ListBox, but nothing displays.
The DataContext of the whole page is defined like so:
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
Then the HubSection is like so:
<HubSection x:Name="HubSec1">
<DataTemplate>
<ListBox x:Name="ListBox1" ItemsSource="{Binding players}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Path=PlayerName, Mode=TwoWay}"/>
<ComboBox ItemsSource="{Binding Path=ListOfElements}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</HubSection>
If I define the ComboBox in the same way but outside the ListBox, it will display the string elements of "ListOfElements" properly.
But in this ListBox, the ComboBox is empty. So my guess is that having defined an ItemsSource for the ListBox, it is not possible to override it.
I have tried to define a DataTemplate but was not successful doing so, but it might be the good solution (and I did not proceed properly)
What am I missing ?
Edit :
The ComboBox items is an ObservableCollection. It is not part of the "players" class.
Here is how I added these elements to the DefaultViewModel
DefaultViewModel.Add("players", players);
DefaultViewModel.Add("MyItemsList", ListOfElements);
You can walk up the visual tree and bind to an ancestors datacontext:
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
EX:
{Binding Path=ListOfItems, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}
that should give you the datacontext that the listbox has, so assuming your ListOfItems exists in that data context.
Or you can name your control, and then bind to its datacontext by element name:
{Binding ElementName=mySourceElement,Path=ListOfItems}
It can be a little bit tricky to create a good working binding in Windows Apps. A widely used work around is to use the Tag property.
<ListBox x:Name="ListBox1" ItemsSource="{Binding players}" Margin="0,184,0,0" Tag="{Binding Path=ListOfElements}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Path=PlayerName, Mode=TwoWay}"/>
<TextBox Text="{Binding Path=Tag, ElementName=ListBox1}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
A binding to an element wirh the specific name will work always. And the ListOfElements should be in the scope of the ListBox so you can use the Tag property as a proxy. If you need to bind more than one property, you can also use dummy XAML elements:
<Border Tag="{Binding ...}" Name="dummy1"/>

How to add more items to ItemsControl after binding

In my C# windows phone app, I create a binding to bind a list of string to ItemsControl.
// MyCollections is a List<string>
<ItemsControl x:Name="ContentRoot" ItemsSource="{Binding MyCollections}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding }" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It works. But My question is how can I add my own item (e.g. 'Click to add more') to this ItemsControl after it is binded?
There are two answers to this question:
Use an ObservableCollection instead of a List, as it will notify the UI when items are added/removed from it. Then you just add your new item to the list in the view model.
Use a CompositeCollection so you can have the "additional" item without modifying the actual collection.
Normally you would do 1, but since you want a "Click to add more" type of option, CompositeCollection is probably the way to go.
Since you metioned windows phone (but tagged WPF) you may want to look at this post for how to write your own CompositeCollection object: how to do a CompositeCollection in WP8?
Use CompositeCollection to add additional items in your XAML. This should work:
<StackPanel x:Name="stackPanel">
<StackPanel.Resources>
<CompositeCollection x:Key="myCollection">
<CollectionContainer Collection="{Binding DataContext.MyCollections,
Source={x:Reference stackPanel}}"/>
<ContentControl Content="Click to add more"/>
</CompositeCollection>
</StackPanel.Resources>
<ItemsControl x:Name="ContentRoot"
ItemsSource="{StaticResource myCollection}"/>
</StackPanel>

Optimal way to populate a large number of textboxes in MVVM

I'm currently creating a WPF application using MVVM. I have a large number of textboxes in a window (about 20) that need to be bound to specific elements in a list and need to be populated all at once. Normally I'd push them into an array and populate them that way but I can't do so without breaking the MVVM model. Is there a quick and efficient way I can do this while still adhering to MVVM?
You could bind your list to an ItemsControl and change it's item template to be a TextBox.
<ItemsControl ItemSource={Binding aList}>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I don't see why strictly this would break MVVM, if instead of using an Array you used a List, put it in your ViewModel and then use indexed binding to bind to specific elements.
Something like:
<StackPanel>
<TextBox Text="{Binding MyViewModelList[0]}">
<TextBox Text="{Binding MyViewModelList[1]}">
<TextBox Text="{Binding MyViewModelList[2]}">
</StackPanel>
or if you want something more dynamic, instead of List, put an ObservableCollection in your VM, and bind to it in an ItemsControl with a DataTemplate.
<ItemsControl ItemsSource="{Binding Path=MyViewModelObsCol}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Syntax might not be 100% as I don't have an IDE to test, but something along these lines might be what you're after.
If you are trying to populate textbox on the bases of selection in list box try this
Another option is creating a COllection view source which i don't think you will require here

How to create a DataTemplate with a StackPanel of a variable number of StackPanels

I am trying to create a DataTamplate which should contain a StackPanel with a certain number of StackPanels.
<DataTemplate x:Key="DataTemplate">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Rectangle Fill="Aqua" Margin="2" Height="100" Width="50" VerticalAlignment="Top" HorizontalAlignment="Left"/>
</StackPanel>
</StackPanel>
</DataTemplate>
The code snippet above is just for a better understanding of my desired result, as the elements in the imbricated StackPanel will be binded.
This generates the following error message:
VisualTree of ItemsPanelTemplate must be a single element.
Any alternatives that could work?
You should ItemsControl with ItemsSource bound to your source list. In ItemsControl.ItemsPanel you can set which panel you want to use for items. In your case you should use StackPanel with Orientation=Vertical as ItemsPanel. See first sample here. But vertical StackPanel is already default ItemsPanel so you can omit it.
Inner StackPanel should be specified as ItemTemplate in your ItemsControl.
Your XAML should look like this:
<ItemsControl ItemsSource="...">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- properties of your object should go here -->
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

WPF Databinding stackpanel

Im a beginner in WPF programming, coming from .NET 2.0 C#.
Im trying to make a horizontal StackPanel which should be filled with data from a table in a database. The problem is that I want it to display an image with some text from the table below and then stack those two items horizontally.
Here's some pseudo-code to display what I want to do:
<StackPanel Orientation="horizontal" ItemsSource="{Binding Path=myTable}">
<StackPanel>
<Image Source="User.png"/>
<Label HorizontalAlignment="Center" Content="{Binding Path=UserName}"></Label>
</StackPanel>
</StackPanel>
I simply cannot figure oout how to do this.
Julien's answer is correct for your written description, however, looking at your XAML, it appears you are looking for something like the following:
<DataTemplate x:Key="UserDataTemplate">
<StackPanel>
<Image Source="User.png"/>
<Label HorizontalAlignment="Center" Content="{Binding Path=UserName}" />
</StackPanel>
</DataTemplate>
<ItemsControl x:Name="UserList" ItemTemplate="{StaticResource UserDataTemplate}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
You definately need an ItemsControl (or some derivation of) to bind your source to. Then you can change the the orientation by setting it's items panel (which I believe is a VirtualizingStackPanel with Vertical orientation by default) so just set it to a VirtualizingStackPanel with Horizontal Orientation. Then you can set the ItemsTemplate for each of your items to the layout you desire (an image stacked on top of text bound from your database).
Basically, you want to use a control capable of displaying an enumeration of objects. The control capable of this is the class ItemsControl and all of its descendants (Selector, ListBox, ListView, etc).
Bind the ItemsSource property of this control to a list of objects you want, here a list of users you've fetched from the database. Set the ItemTemplate of the control to a DataTemplate that will be used to display each item in the list.
Sample code:
In a Resources section (for example Window.Resources):
<DataTemplate x:Key="UserDataTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="User.png"/>
<Label HorizontalAlignment="Center" Content="{Binding Path=UserName}" />
</StackPanel>
</DataTemplate>
In your Window/Page/UserControl:
<ItemsControl x:Name="UserList" ItemTemplate="{StaticResource UserDataTemplate}" />
In your code behind:
UserList.ItemsSource = ... // here, an enumeration of your Users, fetched from your db

Categories