Binding Converter using index of item in list - c#

I have a scenario which requires me to bind a list of objects to a grid. I would like the behaviour as such that :-
• When an item is added to the list it moves to the next available space in the grid. (like a wrap panel)
Shown below is my current XAML.
<ItemsControl ItemsSource="{Binding Path=Contents}" Grid.Row="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<cc:DynamicGrid ShowGridLines="True" RowCount="{Binding Path= SelectedGridStyle.RowCount}" ColCount="{Binding Path=SelectedGridStyle.ColCount}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Currently this puts all of the items on top of each other because I have not specified a row index or column index. I plan to add the following to rectify this
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Grid.Column" Value="{Binding Path=ColumnIndex}"/>
<Setter Property="Grid.Row" Value="{Binding Path=RowIndex}"/>
<Setter Property="Grid.ColumnSpan" Value="{Binding Path=ColumnSpan}"/>
<Setter Property="Grid.RowSpan" Value="{Binding Path=RowSpan}"/>
</Style>
</ItemsControl.ItemContainerStyle>
I have the following code which will allow me to calculate the rowIndex and columnIndex from the index in the list.
int m_ColumnCount = 3;
int rowIndex = index / m_ColumnCount;
int columnIndex = index - (rowIndex * m_ColumnCount);
I am attempting to create a binding converter using the above to set these values. However this calculation depends on values held in the view model for the number of columns and rows. Is there a way to get these values?Can the above be achieved or is there a better solution?

Check out this - http://www.switchonthecode.com/tutorials/wpf-tutorial-using-multibindings
You will see about half way down the use of the multibinding, it's pretty simple. Then they show you how to create a converter for it. Sounds like you are familiar with converters so I think that article will get you moving.

If a binding needs more than one value you could use a MultiBinding. To bind to the main viewmodel you can use a RelativeSource targeting the ItemsControl.

Related

ItemsControl, ItemContainerStyle and ItemTemplate

Since I'm still struggling with understanding how ItemContainerStyle works, I tried to go to the root component that defines its behavior, that is ItemsControl.
The simplest application of style I can think of is trying to apply a couple of settings, let say the Background and the Foreground to the item.
<Window.DataContext>
<local:VM></local:VM>
</Window.DataContext>
<DockPanel >
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Foreground" Value="red"/>
<Setter Property="Control.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Window>
The underlying class for the data is:
public class VM
{
public ObservableCollection<string> Items { get; set; } = new ObservableCollection<string>();
public VM()
{
Items.Add("first");
Items.Add("second");
Items.Add("third");
}
}
The result:
Ok, background is not applied, but this is not what I wanted to check and BTW in WPF there seem to be more exceptions than rules. (And BTW2 i've already fighted with assigning the background of a ListBox selected item, that requires to retemplate the whole thing, maybe here it's similar? If you know the answer it's appreciated, but I leave it for now because it's taking me off track).
Let's also have a look at the Visual Tree:
That is, for ItemsControl the items don't get a 'wrapper element'; if we do the same with a ListBox, for each item of the collection, it will be constructed a ListBoxItem.
Now let's try to template the item by adding this (just after </ItemsControl.ItemContainerStyle>) :
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<Label MaxWidth="100" Content="{Binding}"/>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
This is the result (they are moved in the center because of the MaxWidth="100"; I wanted to see if there was something behind):
The style is not applied anymore. Let's have a look at the Viusal Tree:
This visual tree is not surprising, we just replaced default representation that before was a TextBlock. In its place now we find a Label with its own standard sub-tree.
What's surprising is that at least Foreground should apply to the label too, but unfortunately it doesn't.
What's going on then?
I've read a very similar question here:
Can't use ItemTemplate and ItemContainerStyle together?
It differs from this in that it tries to assign the ContentTemplate. Since I'm still struggling with the basic behavior here (and I didn't understand the answer there except that there is some sort of copy-problem) I decided to put this more basic question.
However it seems there is a style-targeting problem here and not a copy problem; this is because if I keep the ItemTemplate, but replace the Label with a TextBlock (that leads to the very same VisualTree of the non-templated version) I get back my foreground red color!
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<TextBlock MaxWidth="100" Text="{Binding}"/>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
Getting warmer?
So it seems that the framework checks if the component is TextBlock and if not doesn't apply the style.
But this is the default behavior when applying implicit styles: a stile with (TargetType == the type of the control being styled).
In this case it seems like the framework assumes that the TargetType is TextBlock, and never reconsiders this assumption even if ItemTemplate is set.
In order to better understand how the style-target works here I tryed to set the style's TargetType explicitly, who knwos, so let's try this:
<ItemsControl.ItemContainerStyle>
<Style TargetType="Label">
<Setter Property="Label.Foreground" Value="red"/>
<Setter Property="Label.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
See the TargetType="Label"? Great. It gives the error:
Cant apply to ContentPresenter a style intended for Label.
(translated from italian, maybe not the exact wording in english. plz replace with the exact one if you have it at hand).
That is, it expects this:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Label.Foreground" Value="red"/>
<Setter Property="Label.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
It somewhat makes sense, because the root node of each item, according to the visual tree shown before is actually ContentPresenter.
At this point I'm quite confused: how is it supposed to work? The idea for the moment is that it doesn't.
The behavior for the subclasses like ListBox seems to be more sensible: it styles the container of the item; here a container for the item doesn't exist. That's just my guess because i couldn't find any documentation saying this.
You're looking at your items and thinking about them when setting the ItemContainerStyle.
But of course this is their container you're setting a style on. The container of each item. You don't really care about your container because it's not doing much.
Maybe a concrete example of a use case would be clearer than theory.
If you look at:
https://i.imgur.com/UZ6Nqrc.png
Those red and blue rectangles are units in this game.
Those are a variety of nato symbols indicating infantry, artillery cavalry etc.
An itemcontainerstyle is used to position them.
The whole panel on the left has an itemscontrol with a canvas as it's itemspanel ( instead of the default stackpanel ).
There is a viewmodel for each unit and a collection of these is bound to the itemssource of that itemscontrol.
A unit viewmodel has an X and Y property which is used to position the unit within that canvas.
The position of a unit is defined by a point which is the centre of it's view. Glossing over exactly why that is, I think this is interesting because the unit's viewmodel doesn't need to calculate the offset from centre to top left corner. This is done by a converter in the view and applied using a style:
<Style TargetType="ContentPresenter" x:Key="CenteredContentPresenter">
<Setter Property="Canvas.Top">
<Setter.Value>
<MultiBinding Converter="{local:MultiAddConverter}">
<Binding Path="Y" Mode="TwoWay"/>
<Binding Path="ActualHeight"
Converter="{local:MultiplyConverter Multiplier=-.5}"
RelativeSource="{RelativeSource Self}"
Mode="TwoWay" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{local:MultiAddConverter}">
<Binding Path="X" Mode="TwoWay"/>
<Binding Path="ActualWidth"
Converter="{local:MultiplyConverter Multiplier=-.5}"
RelativeSource="{RelativeSource Self}"
Mode="TwoWay" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
Elsewhere, in the map editor trees are positioned in a similar manner.
The ItemContainerStyle is applied to the item containers that gets created by the virtual GetContainerForItemOverride method.
In the ItemsControl base class, this method returns a ContentPresenter:
protected virtual DependencyObject GetContainerForItemOverride()
{
return new ContentPresenter();
}
In the derived ListBox class, it returns a ListBoxItem:
protected override DependencyObject GetContainerForItemOverride()
{
return new ListBoxItem();
}
The TargetType of the ItemContainerStyle must match the type of the DependencyObject returned from this method. Otherwise, you'll get an exception when the style is applied to the container(s) at runtime.

MVVM - Hide Datagrid column based on column name with autogeneratecolumns = True

I have a DataGrid that's bound to Datatable, and I want to uniquely identify rows in the DataTable using the ID, but I don't want it to be shown in the DataGrid
What I reached so far by searching and excluding:
Data columns are not predefined, so, I have to use AutoGenerateColumns=True, hence, I can't define the columns manually and set the Visibility property to False.
I can't use List or ObservableCollection to define private ID member, because the data are dynamic.
I am following MVVM so, I can't use AutoGeneratingColumns event handler directly and can't expose the View to the ViewModel.
The closest I get to an answer is using DataTrigger to set Visibility to False using CellStyle, but it just hid the cells, not the entire column, and I also tried it for DataGridColumnHeader and it didn't work:
code:
<Style x:Key="ColumnStyle" TargetType="DataGridColumnHeader">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Value}" Value="id">
<Setter Property="Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
How to do it while maintaining the previous conditions?
Thanks in advance
EDIT:
I fixed the code for DataGridColumnHeader using Path=Column.Header which doesn't make sense to me but it's irrelevant; Still, there and empty column standing there, with no idea how to remove it.
It sounds like you want to track the selected Item. If you want to track the "selected Element", you have to use a CollectionView.
WPF controls do not direcly bind to collections. They bind to a CollectionView. And if you do not give them one, they will create one themself from whatever collection you hand them. If you want sorting, filtering, ordering and selection tracking, CollectionView is the droid you are looking for:
https://msdn.microsoft.com/en-us/library/system.windows.data.collectionview.aspx
Just take control of it's creation and expose it (rather then the raw collection).
I found a solution by applying this style:
<Style x:Key="ColumnStyle" TargetType="DataGridColumnHeader">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Value}" Value="id">
<Setter Property="Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
to DataGridCell and to DataGridColumnHeader and allocating the column in the end of the table this removed the empty column from the middle of the table.

Unknown attachable member 'Grid.Row'and 'Grid.Column'

I'm a long time WPF designer yet new to windows app development. I'm trying to bind a collection of objects onto a grid yet keep getting the error Unknown attachable member '(Grid.Row)' on element 'FrameworkElement' and Unknown attachable member '(Grid.Column)' on element 'FrameworkElement'.
Can someone please explain to me the how to set the various Grid attached properties via style?
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<!-- Column and row definitions omitted for brevity -->
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="FrameworkElement">
<Setter Property="(Grid.Row)" Value="{Binding Row}" />
<Setter Property="(Grid.Column)" Value="{Binding Column}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Don't use a PropertyPath. All you need is a qualified Owner.Property string.
<Setter Property="Grid.Row" Value="{Binding Row}" />
Taken from PropertyPath XAML Syntax
Some style and template properties such as Setter.Property take a
qualified property name that superficially resembles a PropertyPath.
But this is not a true PropertyPath; instead it is a qualified
owner.property string format usage that is enabled by the WPF XAML
processor in combination with the type converter for
DependencyProperty.
It turns out that there are actually 3 problems with the code I posted above.
As #LPL correctly identified Setter.Value takes a qualified property name where a PropertyPath was being used. The fix here is to drop the parentheses: <Setter Property="Grid.Row" ... /> and <Setter Property="Grid.Column" ... />.
The second issue is with the style target type. It turns out that the metro Grid attached properties can't be applied to FrameworkElement's. The solution here is to update the target type with something more specific: <Style TargetType="ContentPresenter" />.
Finally as with Silverlight, the value property of metro setters don't support bindings. Consequently even after fixing the previous two errors, the setter is actually trying to set the grid attached properties to an instance of type Binding. While not as straight forward, all the details of a solution may be found here. In summary you can use the setter to set a custom attached property, which will in turn set up any desired binding.

Set ListView row background

I have the following ListView:
<ListView>
<ListView.View>
<GridView/>
</ListView.View>
</ListView>
I want to set the background of the second row (for example) to red programmatically.
What should I do?
Thanks.
You can use a similar approach:
int index = 1;
ListViewItem row = ListView.ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem;
row.BackGround = Brushes.Red;
You can use AlternationCount to store the index of the row in AlternationIndex, and then use a trigger to set the background of the second row (at index 1) to red:
<ListView AlternationCount="{x:Static sys:Int32.MaxValue}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
...
</ListView>
AlternationCount defines the number of rows to count before restarting to zero. For example, if set to 2, the indexes for the rows will be 0 1 0 1 0 1 ..., allowing you to paint to red only the odd or even rows. Setting it to MaxValue will never restart the count, effectively setting AlternationIndex to the actual index.
Please clarify one thing here nick. Can you tell me whether you want to just make the color of the second row to red. If that is the case you can iterate through the list view and then keep a count variable on the top and then when the count == 1 then you can change the list view.background color to red. if you have tried this then can you tell me what happened. Does it ended with error or something or nothing happened.

Listview Trouble - Tooltip Needed

I am building a WPF application in C# using VS2010
I have a listview that contains items from a database , and each item contains a field called (Name) and another field called (Time) .
Back in the database , each item has a third field called (Description) too ...
Now what I want is :
When I choose an item from the listview , a tooltip is shown and it contains the data from the third field ..
How can I have various tooltips on one listview - one tooltip for each item - ??
How can I deal with my database ??
Thank You
Setting the Tooltip for a ListViewItem can be done like this
<ListView ...>
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ToolTip" Value="{Binding Path=Name}"/>
</Style>
</ListView.ItemContainerStyle>
<!-- ... -->
</ListView>

Categories

Resources