Databind DockPanel - c#

I'm trying to databind a Dockpanel in wpf to a viewmodel collection. I'm using this in order to create a customizable form and so I will not know how many children should be in the dockpanel until runtime.
The problem that I am having is that the attached property DockStyle.Dock doesn't seem to be getting applied when I set it from within a data template. The following xaml is a simplified version of what I am doing. I would expect the first button to fill up the top portion of the screen but what really happens is that they are stacked horizontally. Even hardcoding the DockPanel.Dock property has no effect on the layout of the buttons. When I look at the Visual Tree in XAMLPad I notice that there are ContentPresenters as children of the DockPanel instead of the buttons. Do ContentPresenters get layed out differently then other elements? Is there another technique to databinding to DockPanels?
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Grid>
<ItemsControl >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel LastChildFill="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button DockPanel.Dock="{Binding}" Content="{Binding}"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<sys:String>Top</sys:String>
<sys:String>Bottom</sys:String>
<sys:String>Left</sys:String>
<sys:String>Right</sys:String>
<sys:String>Top</sys:String>
<sys:String>Top</sys:String>
<sys:String>Top</sys:String>
</ItemsControl>
</Grid></Page>

The reason that the items aren't docked is because the control in the DataTemplate is not a direct child of the DockPanel. I believe that the ItemsControl creates one ContentPresenter for each item, just like how a ListBox creates one ListBoxItem for each item.
You could try using the ItemContainerStyle to dock the controls. I think that any properties set on it should be set on the ContentPresenter. Something like this might work:
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="DockPanel.Dock" Value="{Binding}" />
</Style>
</ItemsControl.ItemContainerStyle>
...
</ItemsControl>
I'm not sure if binding a string to the Dock property will work or not, though. You might try using the values in the enum directly to see if that helps as well.

This is because your Button is wrapped by ItemsControl in ContentPresenter which does not have DockPanel.Dock set. Try setting ItemContainerStyle to something like this:
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="DockPanel.Dock" Value="{Binding}"/>
</Style>
</ItemsControl.ItemContainerStyle>

Related

Get Appium automation element in runtime

I need to create automation system-level test for control.
Control is set by style and have ListView with separate ListViewItem template.
And my goal is to get text from the header inside this ListViewItem.
Here is what I have in XAML (code simplified)
<Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border>
<Grid>
<ScrollViewer x:Name="ScrollViewer">
<ScrollViewer.TopHeader>
<StackPanel>
<ListView ItemsSource="{TemplateBinding SomeSource}"
ItemTemplate="{StaticResource MyTemplate}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</StackPanel>
</ScrollViewer.TopHeader>
</ScrollViewer>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And template for ListViewItem is something like:
<DataTemplate>
<Grid>
<Grid>
<TextBlock x:Name="TextName"
AutomationProperties.AutomationId="{Binding SomeId}"
Text="{x:Bind SomeText}"/>
</Grid>
</Grid>
</DataTemplate>
Of course ListViewItems are generating automatically in runtime. They are binded to according property and got needed template.
But in test I can't reach my TextBlock anyhow (ById, FindElementById and so on).
I can find elements by firstly finding ListView by class (FindElementByClassName), then ListViewItems by class, then TextBlock by class, but I think it is not the right way. Because in future structure of control could be changed and it will be harder to support tests.
Control have AutomationPeer as the Grids too.
So do you have any ideas why I can't get my simple TextBlock or even ListViewItems by their AutomationId?
Problem was resolved by creating custom Automation peer for conrtol. Control inherits ListView (wasn't clear in problem description). So there was problems with listview inside listview-inherited control by Automation id.
Also was helpfull adding Thread.Sleep() or Task.Delay() for a couple of seconds after element shown but before element finding by WinAppDriver.
Looks like test environment need more time to bind and find elements.

WPF MVVM Binding dynamic control in code behind and pass in View

I am working on WPF application using MVVM. I have two page. I have multiple UserControls in a page 1, on selection of UserControls from page 1, I want to show that selected userControl in 2nd page. Below are my code.
ViewModel Code
public RelayCommand<string> OnClickSelectWidgetCommand => new RelayCommand<string>((setUserControlName) =>
{
using (new CursorWait())
{
var MyContentControl = setUserControlName;
MessageBox.Show(MyContentControl);
//How to render UserControl to View?
}
}, true);
Here in above code I get the UserControl name in setUserControlName variable. Now how to bind that UserControl to XAML page? Below are my code that I have tried.
View Code
<StackPanel Background="Black" VerticalAlignment="Top">
<Border Name="UserControl1BorderLow" BorderBrush="White" BorderThickness="0" >
<ItemsControl ItemsSource="{Binding LowCollection}" Margin="4,0" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:UserControlColumn1XL HorizontalAlignment="Left" Margin="2" />
<!--what can I do here in above line to make it dynamically render the userControl in place of UserControlColumn1XL-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border></StackPanel>
Above code, In DataTemplate what need to be change to bind UserControls dynamically?
There are two ways to solve this, one involves setting the template based on your data type (DataTemplates) and the second involves setting it based on the data itself (DataTriggers).
In the first case your LowCollection should be an array of objects, or some base class that your view models are all derived from (ViewModel1, ViewModel2 etc). In this case you can get rid of your itemtemplate altogether and just add DataTemplates to specify how each of the items in your ItemsControl should be represented:
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<UserControl1 />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<UserControl2 />
</DataTemplate>
... etc...
In the second case you need to set a template based on the value of some property in your view model. In this case you do need to set the ItemTemplate, and you give it a Style which uses data triggers to set an appropriate DataTemplate:
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}">
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<DataTrigger Binding="{Binding YourProperty}" Value="YourValue1">
<Setter Property="ContentTemplate" Value="{StaticResource YourDataTemplate1}" />
</DataTrigger>
<DataTrigger Binding="{Binding YourProperty}" Value="YourValue2">
<Setter Property="ContentTemplate" Value="{StaticResource YourDataTemplate2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
</DataTemplate>
</ItemsControl.ItemTemplate>
The relevant parts to note here are that there is a property in your view model called YourProperty which can have two values i.e. YourValue1 or YourValue2; the style above then selects either YourDataTemplate1 or YourDataTemplate2, depending on the value of YourProperty.

Accurate scrollbar control in an ItemsControl with a VirtualizingStackPanel

I have an Itemscontrol using a VirtualizingStackPanel to display a huge (and growing) list of items:
<ItemsControl Grid.Row="1" Name="ConversationItemsControl" VirtualizingStackPanel.VirtualizationMode="Recycling">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:Message />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The virtualization is working like a charm, but i cannot get the management of the scrollbar right. If I try to programmatically (e.g. on load) scroll to the bottom like i do in non-virtualized StackPanels:
var scrollViewer = VisualTreeHelper.GetChild(ConversationItemsControl, 0) as ScrollViewer;
scrollViewer.ChangeView(null, double.MaxValue, 1f, true);
the scrollviewer tries to scroll to the bottom, but does not do so completely - it always stops a bit before the "real" bottom. This makes sense in a way since VirtualizingStackPanels are using the scroll value to determine which items to render, but it is totally grinding my gears and unacceptable for end users.
How can I scroll to the "real" bottom? What do I have to do if i want to scroll exactly so far down that the top of a certain item is at the top of the viewport (unless the "real" bottom is too close, naturally)?
This is because the built-in ItemsControl class doesn't support virtualization. You can try a ListBox instead, which uses UI virtualization by default.
If you don't want to have selection behaviour, just set:
<ListBox x:Name="lbCustom">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
and then something like:
lbCustom.ScrollIntoView(lbCustom.Items[lbCustom.Items.Count - 1]

Binding ZIndex from DataTemplate

I have a View that is basically setup like this:
<Grid>
<ViewBox>
<Grid>
<ItemsControl ItemsSource="{Binding MyItems}"
ItemTemplate="{Binding Source={StaticResource MyItemsDataTemplate}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</ViewBox>
</Grid>
The DataTemplate used here can be reduced to this:
<DataTemplate x:Key="AreaItemDisplayDataTemplate">
<Canvas Grid.ZIndex={Binding Z}>
<Grid>
// an shape is displayed here...
</Grid>
</Canvas>
I would now expect the ZIndex to be bound to the Z property of the individual items. When i debug the code, i can also see, that the Z property getter is accessed when i would expect it (whenever i raise the propertychanged event for it, eg) so i assume the binding to work correctly.
However, the ZIndex is not working as expected. The binding to the value has no effect on the actual displayed Z Order. Where am i going wrong with this code?
The content of the DataTemplate gets wrapped in a ContentPresenter so the Canvas in the DataTemplate isn't a direct child of the ItemsPanel Grid. That is the reason the ZIndex property doesn't do anything.
Move the ZIndex Binding to the ItemContainerStyle and it should work.
<ItemsControl ItemsSource="{Binding MyItems}"
ItemTemplate="{Binding Source={StaticResource MyItemsDataTemplate}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Grid.ZIndex" Value="{Binding Z}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>

Canvas as ListBox ItemTemplate

My environment is windows phone 7.1.
I have the following code:
<ListBox ItemsSource="{Binding Path=Items}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Black" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Canvas Width="200" Height="400"
Canvas.Top="400"> <====== This is not working
... Some content ...
</Canvas>
</DataTemplate>
</ListBox.ItemTemplate>
There is a ListBox that has a Canvas as ItemsPanel.
The ListBoxItems itself are also of type Canvas. For the ListBoxItems I set Canvas.Top =400, i expect the items to show with an offset of 400 in the ItemsPanel.
Unfortunately this doesn't work, the items are rendered at an offset of 0 as shown in this image (the ItemsPanel is black, the colorful rectangle is a listitem):
Why arent the ListBoxItems rendered at an offset of 400?
You are setting the Canvas.Top on the contents of the ListBoxItems not the actual items
When using a canvas as item panel you have to remember that your datatemplated objects are wrapped in ListboxItems
ListBox
Canvas <- your itemtemplate
ListBoxItem
Canvas <- your datatemplate
solution:
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Top" Value="400"/>
</Style>
</ListBox.ItemContainerStyle>
Try adding this to your ListBox
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
Because you see, the Black Area is the ListBox, but not your ListBoxItem. Due a "commonly known bug", if we still can call it that, the ListBoxItem doesn't stretch, unless you add the code above.

Categories

Resources