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.
Related
I'm new to WPF MVVM and I've been working my way to an almost complete application. There's only one issue, and I can't seem to figure out what I'm doing wrong. Let me add that I've only got a Winforms background and Databinding is something I've never needed to do, so if the solution seems obvious, that's why.
<!-- The list of download packages. -->
<ListBox x:Name="PackagesList" DockPanel.Dock="Left" Width="120" ItemsSource="{Binding ViewRaster.RasterPackages}">
<!-- Each individual package -->
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<Image Height="16" Width="16" Source="{Binding PackageImage}"/>
<TextBlock Text="{Binding PackageName}"/>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Tag" Value="{Binding PackageDownloads}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
I've got a list of packages within the object ViewRaster as you can tell from the ItemSource of the listbox. With each package of the item source I create a listbox item that contain an image and a textblock with the name of the package, and the image of the package.
Now next, there's the "ItemContainerStyle", which I assumed would have worked the exact same way -- namely, that I could use the properties of each individual package, as was bound from the ItemsSource.
I do not seem to be able to access the individual "Package" as I would within the ItemTemplate -> DataTemplate. It's neccessary that I have the ListBoxItem have either a "Tag" or a "DataContext" set to the "PackageDownloads".
From the designer it's telling me that it's unable to find "PackageDownloads" in the Data Context of my View, but I'm not in the DataContext of my view, I'm in the DataContext of the ItemsSource.
Why is this? How can I fix this?
You don't need to explicitly set a ListBoxItems's DataContext or its Tag property to get access to the selected item from the RasterPackages collection.
The selected RasterPackage object is directly accessible via the ListBox's SelectedItem property:
<ListView
ItemsSource="{Binding ElementName=PackagesList, Path=SelectedItem.PackageDownloads}">
I have an ItemsControl nested inside a ListBox.ItemTemplate. The top ListBox is data-bound to an ObservableCollection. The collection is essentially a Purchase which contains Formulae which in turn contain individual products.
Now, if an individual product inside a formula is clicked, I would like it to be deleted from its formula. So, in the event handler, I should be able to determine the product's position using:
var index = [theItemsControl].ItemContainerGenerator.IndexFromContainer((sender as Button).TemplatedParent);
However, in my event handler, I do not how how to find the containing ItemsControl. I cannot give it a fixed name, since it is itself a part of a ListBox.ItemTemplate, meaning there will be multiple instances.
This is my XAML, stripped of any style-related stuff:
<ListBox x:Name="lbBasketFormulae" ItemsSource="{Binding Path=Purchase.Formulae}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<!-- Formula Title -->
<StackPanel Orientation="Horizontal">
<Label Width="30" Content="{Binding Path=Quantity}"></Label>
<Label Content="{Binding Path=FormulaType, Converter={StaticResource formulaTypeToNameConverter}}"></Label>
</StackPanel>
<!-- Formula Products -->
<ItemsControl ItemsSource="{Binding Path=Products}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="bnBasketFormulaProduct" HorizontalContentAlignment="Left" Content="{Binding Path=Name}" Click="bnBasketFormulaProduct_Click">
<Button.Template>
<ControlTemplate TargetType="Button">
<TextBlock Text="{TemplateBinding Content}" />
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
So, my question is: How do I find the position of my product inside my formula and the position of that formula inside the purchase, so that I can remove it from the ObservableCollection? (Changing the collection should then automatically reflect to the UI since it is data-bound.)
Many thanks in advance for any advice or hints!
Regards,
Chris
It is true that you should be able to do it via the viewmodel. But if you want you could use:
VisualTreeHelper.GetParent(myUserControl);
The answer is you don't find any positions of anything using ItemContainerGenerator.IndexFromContainer. Your problem is clearly taking place inside ViewModel and therefore you should seek for selected item or whatever inside your ViewModel and not in ListBox or code behind of Window.
Therefore I suggest you to create a proper ViewModel with proper Bindings.
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>
I have an odd scenario.
I need to create a StackPanel in codebehind. I then need to have that stackpanel bound to the UI in xaml.
Normally I would just use a ContentControl for this. But it has focus issues (it cannot blocked from the tab order Focusable="False" has no effect). I also tried a usercontrol, but that had the same issues.
So I need to use some other kind of control. I have decided on a Panel. (StackPanel seems as good as any of the panels.)
However, I can't seem to find a way to bind to my "In Code" stack panel in my Xaml?
Is there a way to do this? (WITHOUT using a contentcontrol or usercontrol)
it cannot blocked from the tab order Focusable="False" has no effect
What about IsTabStop?
Also the most lightweight thing to use is a ContentPresenter which is what i would use.
Tested this in KAXAML, and the focus doesn't go to any of the items defined in the ContentPresenter or ContentControl when TAB is pressed.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBox>aaaaa</TextBox>
<TextBox>bbbbb</TextBox>
<ContentControl Focusable="False">
<ContentControl.ContentTemplate>
<DataTemplate>
<StackPanel Focusable="False" Background="Red" Width="100" Height="50"></StackPanel>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
<ContentControl Focusable="False">
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBox Focusable="False">hello</TextBox>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
<ContentPresenter Focusable="False">
<ContentPresenter.Content>
<TextBox Focusable="False">hello</TextBox>
</ContentPresenter.Content>
</ContentPresenter>
<TextBox>ccccc</TextBox>
<TextBox>ddddd</TextBox>
</StackPanel>
</Page>
I know this question has been asked countless number of times, but I don't understand half of what their problem is or how to follow their example.
The one I did find her name Rachel, posted a blog about it but her explanation was too brief..
http://rachel53461.wordpress.com/2011/07/17/wpf-error-specified-element-is-already-the-logical-child-of-another-element-disconnect-it-first/
Here was what I had before trying to follow here example:
<Window x:Class="Graph.View.MainView.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
Title="Main" Height="350" Width="525"
xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking">
<DockPanel LastChildFill="True">
<DockPanel>
<Label Content="{Binding ScreenContent}" Grid.Row="1" HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch"/>
</DockPanel>
</DockPanel>
Because ScreenContent is having a logical parent I can't reuse it. After trying to follow Rachel's example:
<Window x:Class="Graph.View.MainView.Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
Title="Main" Height="350" Width="525"
>
<Window.Resources>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DockPanel>
<Label Content="{Binding ScreenContent}" Grid.Row="1" HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch"/>
</DockPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<DockPanel LastChildFill="True">
<Label Style="{StaticResource MyCustomContentControl}"/>
</DockPanel>
</Window>
How can I fix it? It isn't showing anything at all...
Thanks.
Please answer me what is ScreenContent property ... is it string? or some GUI element?
If it is a String then the follow the soltuion below ...
Also there are a few concepts that you must understand...
Label inside Label is a bad UI design.
ContentControls such as Label, Button etc. follow a particular XAML pattern when we want to set their ControlTemplate and DataTemplate.
In your case you want to assign a data context based property ScreenContent. So the DataTemplate way is correct.
However this will not flow to the Label inside your DataTemplate as the outer Label (to which you have applied the Style to) isnt set on its own Content property.
So basically a ContentControl.ContentTemplate works ONLY if a not null Content is set on that ContentControl and inside that DataTemplate (ContentTemplate) the Content works as the DataContext for all the items!
Notice the {Binding} expression below... It is self explanatory!!
<DockPanel LastChildFill="True">
<DockPanel.Resources>
<Style x:Key="MyCustomContentControl"
TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DockPanel>
<ContentPresenter
Content="{Binding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
</DockPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DockPanel.Resources>
<Label Style="{StaticResource MyCustomContentControl}"
Content="{Binding ScreenContent}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
</Label>
</DockPanel>
If it is a UserControl follow the solution below...
Are you assigning it only once (to the given Label)? or you are attempting to assign it as content of such various elements?
If its assigned ONLY once, then the solution above should work.
But if you are planing to assign it as a Content of multiple target elements then you need to follow the Model / ViewModel approach.
All your GUI elements should be hosted in XAML.
Code Behind should not deal with GUI elements.
Code behind works only on the soft copy of the GUI elements i.e. a data specific to fields that GUI (UserControl) represents.
E.g.
Example 1...
Suppose you have a TitleControl which is like a bold, italic WPF Label showing the text of a title. Your DataContext has such TitleControl object held which has some title text set to it.
Then in the ViewModel approach, you should create an instance of TitleControlViewModel as part of your DataContext... something like this...
YourDataContext.MyTitleControlViewModel = new TitleControlViewModel();
Inside the TitleControlViewModel class we should have a public property called MyTitle of type string.
YourDataContext.MyTitleControlViewModel.MyTitle = "My Title";
Now your template specific XAML should look like this...
<DataTemplate x:Key="MyTitleControlDataTemplate">
<local:TitleControl Title="{Binding MyTitle}"/>
</DataTemplate>
and your host panel should look like this...
<ContentControl ToolTip="Title is shown here..."
Content="{Binding MyTitleControlViewModel}"
Contenttemplate="{StatiocResource MyTitleControlDataTemplate}"/>
<ContentControl ToolTip="Same title control is shown here also !!!"
Content="{Binding MyTitleControlViewModel}"
Contenttemplate="{StatiocResource MyTitleControlDataTemplate}"/>
So this way same title control seems to be hosted on the tow content controls above. But if you think really, there are two different instances of TitleControl who are merely representing same MyTitleControlViewModel thus looking like a same control!
Google for how Data Templates, MVVM fit together.
In case you want to reuse ScreenContent (although its already a child of some other element) then you will have to first clone it and use the cloned control.
You can clone a control by first serializing it using XamlWriter and then create a new control by deserializing it using XamlReader, something like this -
string screenContentXml = XamlWriter.Save(ScreenContent );
//Load it into a new object:
StringReader stringReader = new StringReader(screenContentXml );
XmlReader xmlReader = XmlReader.Create(stringReader);
UIElement screenContentClone = (UIElement)XamlReader.Load(xmlReader);
use this screenContentClone in your window.
But, you may find yourself applying workarounds to make it work as there are some limitations in using XamlWriter.Save (like with bindings) - Serialization Limitations of XamlWriter.Save
Here are some other approaches for serialization -
An XAML Serializer Preserving Bindings
XamlWriter and Bindings Serialization
Although your design doesn't look correct as AngelWPF mentiond and you should try and re-design your window, and you might not require doing all this.
UserControls (or any other type of UI control) do not belong in the ViewModel
It would be far better to have an object that you are binding to, and to use a DataTemplate to tell WPF how to draw that object using your UserControl
For example, if you had
ScreenModel ScreenContent { get; set; }
where ScreenModel is a custom class that looked something like this
public class ScreenModel
{
public string Name { get; set; }
...
}
Then you would bind to it using something like this:
<DockPanel LastChildFill="True">
<DockPanel>
<ContentControl Content="{Binding ScreenContent}" Grid.Row="1" HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch"/>
</DockPanel>
</DockPanel>
And use a DataTemplate to tell WPF how to draw the ScreenModel class
<DataTemplate DataType="{x:Type models:ScreenModel}">
<views:ScreenContentUserControl />
</DataTemplate>
or
<DataTemplate DataType="{x:Type models:ScreenModel}">
<Label Content="{Binding Name}" />
</DataTemplate>
Because you are using a Template, WPF will create a new copy of the UserControl whenever its needed, and won't try to use the same UserControl in multiple spots.
Your error is occurring because you are adding the same UserControl (ScreenContent) to the VisualTree more than once through the Content="{Binding ScreenContent}" binding.
Even doing something like switching tabs will probably cause this error because you will be unloading all the UI objects by switching away from the tab, then loading new UI objects by switching back, however your ScreenContent user control already has it's parent set to an old object that no longer exists.