How to uniquely position usercontrols from an ItemsControl (in MVVM)? - wpf

I am trying to present the usercontrol, CharacterEditorView, from within an ItemsControl. (The use of "Canvas" in the ItemsPanelTemplate can be changed, Grid?). The CharacterEditorView is to overlay on top of a character string, so position is crucial. The ItemsControl is:
<ItemsControl Grid.Row="0" Grid.RowSpan="1" ItemsSource="{Binding CharacterPads}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Margin" Value="{Binding Margin}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
where the binding of CharacterPads is defined as:
private ObservableCollection<CharacterEditorViewModel> characterPads = new ObservableCollection<CharacterEditorViewModel>();
public ObservableCollection<CharacterEditorViewModel> CharacterPads
{
get
{
return characterPads;
}
}
I would like to present EACH CharacterEditorView within a specific rectangle known only at the time of its viewmodel creation. That is, each CharacterEditorView will have a different
rectangle to be presented in:
<UserControl x:Class="Nova5.UI.Views.CharacterEditorView"
.................................................................
<Grid Background="Red">
<TextBlock Text="TextBlock" Margin="{Binding ...?????}" />
</Grid>
</UserControl>
What binding is needed to position EACH CharacterEditorView at its own position?
Thanks for any ideas.

If you want to bind the TextBlock's Margin, you need some bindable property of type Thickness within the CharacterEditorViewModel and specify this as the binding target (or, for cleaner code, provide numeric properties in the ViewModel and implement a ValueConverter that creates a Thickness instance out of them).
Alternatively, since the ItemsPanelTemplate is already defined as Canvas, bind those numeric properties directly to Canvas.Left and Canvas.Top, as HighCore suggested.

Related

ItemsControl Not Creating ContentPresenter

Why isn't my ItemsControl creating a ContentPresenter for each item? I'm guessing this is what's making my items not show up (they're set to visible and in the right spot when I inspect using the Live Visual Tree). I'm basically reusing code that works up above in a different ItemsControl and I haven't been able to find anything while searching Google/Stackoverflow with this issue. I can include view model code but I don't think it's related because I see the appropriate values in the Live Property Explorer and can see each WellContainer is in it's appropriate grid cell.
XAML:
<ItemsControl
Grid.Row="1"
Grid.Column="1"
ItemsSource="{Binding Wells}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid
x:Name="m_WellGrid"
Margin="5"
wpf:GridHelpers.RowCount="{Binding RowCount}"
wpf:GridHelpers.ColumnCount="{Binding ColumnCount}">
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter
Property="Grid.Row"
Value="{Binding Path=WellRow}"/>
<Setter
Property="Grid.Column"
Value="{Binding Path=WellCol}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="A"
Margin="4"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Live Visual Tree Inspection:
The ItemsControl is designed to wrap the items in a container only when necessary, that is, when the item is not eligible to be its own container. From your comment we find that WellContainer derives from Control, thus is eligible to be its own container1 and is not wrapped in a ContentPresenter. Unfortunately there's no way to control this behavior directly, but you could subclass ItemsControl and override the ItemsControl.IsItemItsOwnContainerOverride method to modify the default behavior.
1 As we can see in the ItemsControl source code it is enough for the item to be of UIElement type to be eligible to be its own container.

Zero-height of ScrollViewer-templated item inside ItemsControl?

I have an ItemsControl with arbitrary items. Some of the items are wrapped inside a ScrollViewer. The code-behind for these scrollable items makes use of the ViewportWidth (almost equivalent to ActualWidth) and ViewportHeight (almost equivalent to ActualHeight) properties to arrange/size its visual children. This works as long as I don't put the item inside an ItemsControl. When the item appears in an ItemsControl the value of ViewportHeight equals 0 - effectively making my item invisible. Note that I want to arrange the items vertically, giving all items equal height! No fancy stuff, just a regular StackPanel.
The templates are applied automatically using DataType:
<MyControl.Resources>
<DataTemplate DataType="{x:Type MyScrollableItem}">
<MyControlWrappedInScrollViewer Text="{Binding Text}" />
</DataTemplate>
<DataTemplate DataType="{x:Type MyItem}">
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</MyControl.Resources>
<ItemsControl ItemsSource="{Binding MyCollection}" />
The structure of MyControlWrappedInScrollViewer looks something like this:
<UserControl>
<Grid>
<ScrollViewer CanContentScroll="True">
<Canvas />
</ScrollViewer>
</Grid>
</UserControl>
Why does my ScrollViewer get the height of 0? How can I tell my ItemsControl to size the item appropriately? E.g. One item yields a height of the ItemsControl height. Two items yield half of it, and so on.
This did the trick:
<Style TargetType="{x:Type ItemsControl}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<UniformGrid Columns="1" IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>

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

ItemsControl and controls attached properties

I'm trying to implement a diagram with movable/resizeable parts in WPF.
I would like to use ItemsControl with ItemsPanel configured to be "DynamicCanvas".
All you need to know about DynamicCanvas right now is that it acts like a usual canvas - with one exception - it utilizes attached properties to store information about X,Y attributes on its children.
My code:
<ItemsControl IsTabStop="False" ItemsSource="{Binding ElementName=comboBox1,Path=SelectedItem.Source.Table}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<s:TableControl Table="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!--<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">-->
<c:DynamicCanvas SizeHeightToContent="True" SizeWidthToContent="True" ClipToBounds="True" SnapsToDevicePixels="True" PreviewMouseDown="Canvas_MouseDown" IsHitTestVisible="True" Background="Gray" >
</c:DynamicCanvas>
<!--</ScrollViewer>-->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The controls that are being displayed on DynamicCanvas are of my custom type (below only the most important part):
<ContentControl x:Class="SubiektCommerceSynchro.ViewModel.TableControl"
c:DynamicCanvas.Left="{Binding X,Mode=TwoWay}"
c:DynamicCanvas.Top="{Binding Y,Mode=TwoWay}"
Width="450" Height="300"
></ContentControl>
Now the problem and the question:
The part here that doesn't work is with attached properties c:DynamicCanvas.Left(Top).
Lets put it in steps:
1) DynamicCanvas expects its immediate children to have c:DynamicCanvas.Left and c:DynamicCanvas.Top defined
2) ItemsPanel when putting TableControls onto the DynamicCanvas wraps them in some kind of container
3) DynamicCanvas sees no attached properties on its immediate children => treats them as being positioned at (0,0) and renders them effectively unmoveable.
How can I resolve this issue?
Does this help?
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="c:DynamicCanvas.Left"
Value="{Binding X,Mode=TwoWay}"/>
<Setter Property="c:DynamicCanvas.Top"
Value="{Binding Y,Mode=TwoWay}"/>
</Style>
</ItemsControl.ItemContainerStyle>
You have to modify the ControlTemplate of the item wrapper in the ItemContainerStyle. If you set it to simple ContentPresenter, the items will not be wrapped in anything (the contents of the DataTemplate will be pasted directly into the DynamicCanvas).
See this article.

Templating ItemControl items without using ItemsSource

I am trying to build a custom Silverlight ItemsControl. I want the users of this control to add items using XAML. The items will be other UI elements. I would like to add a margin around all added items and therefore I want to add an ItemTemplate.
I am trying to do this using the ItemsControl.ItemTemplate, but that does not seem to be used when binding to elements in XAML, i.e using the ItemsControl.Items property.
However, if I use the ItemsControl.ItemsSource property, the ItemTemplate is used.
Is there anyway to use the ItemTemplate even though I am not assigning ItemsSource?
This is my code so far
<ItemsControl x:Class="MyControl">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate >
<toolkit:WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="20" Background="Red">
<TextBlock Text="Test text"/>
<ContentPresenter Content="{Binding}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<Border>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ItemsPresenter x:Name="ItemsPresenter"/>
<Button Command="{Binding SearchCommand}"/>
</Grid>
</Border>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
And when I use my control
<MyControl>
<Button Content="Button"/>
<Button Content="Button"/>
</MyControl>
This got me a display of the items, with a wrap panel layout but no applied data template.
Then I found this post that mentioned two methods to override.
Son in my code-behind of the class I have now
protected override bool IsItemItsOwnContainerOverride(object item)
{
return false;
}
protected override void PrepareContainerForItemOverride(DependencyObject element,
object item)
{
base.PrepareContainerForItemOverride(element, item);
((ContentPresenter)element).ContentTemplate = ItemTemplate;
}
BUT - this gets me two items, with the style (I.E a red textblock) but no actual content. The buttons in the list are not added. It feels like I am doing something wrong - any pointers on what?
Thanks!
If all you want to do is add some margin then you can just set the ItemContainerStyle instead of specifying a template:
<ItemsControl>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="FrameworkElement.Margin" Value="10" /> <!-- or whatever margin you want -->
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
This will allow you to set any property of the container control (which in the case of an ItemsControl will be a ContentControl) through the style.

Resources