Skip to content

denpadokei/Friendly.WPFStandardControls

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

Friendly.WPFStandardControls

This library is a layer on top of Friendly, so you must learn that first. But it is very easy to learn.

https://github.com/Codeer-Software/Friendly

Getting Started

Install Friendly.WPFStandardControls from NuGet

Install-Package RM.Friendly.WPFStandardControls

https://www.nuget.org/packages/RM.Friendly.WPFStandardControls/


ControlDrivers

Friendly.WPFStandardControls defines the following classes.
They can operate WPF control easily from a separate process.

  • WPFButtonBase
  • WPFComboBox
  • WPFListBox
  • WPFListView
  • WPFMenuBase
  • WPFMenuItem
  • WPFProgressBar
  • WPFRichTextBox
  • WPFSelector
  • WPFSlider
  • WPFTabControl
  • WPFTextBox
  • WPFTextBlock
  • WPFToggleButton
  • WPFTreeView
  • WPFTreeViewItem
  • WPFCalendar
  • WPFDatePicker
  • WPFDataGrid
  • WPFListBox<TItemUserControlDriver>
  • WPFListView<TItemUserControlDriver>
  • WPFTreeView<TItemUserControlDriver>

//sample  
var process = Process.GetProcessesByName("WPFTarget")[0];  
using (var app = new WindowsAppFriend(process))  
{  
    dynamic main = app.Type(typeof(Application)).Current.MainWindow;  
    
    var textBox = new WPFTextBox(main._textBox);
    textBox.EmulateChangeText("abc");

    var grid = new WPFDataGrid(main._grid);  
    grid.EmulateChangeCellText(0, 0, "abc");  
    grid.EmulateChangeCellComboSelect(0, 1, 2);  
    grid.EmulateCellCheck(0, 2, true);  
}  

More samples.

https://github.com/Roommetro/Friendly.WPFStandardControls/tree/master/Project/Test

Data Template ItemsControl

For ListBox, ListView, TreeView, there is a mechanism that supports customization using DataTemplate.

  • WPFListBox<TItemUserControlDriver>
  • WPFListView<TItemUserControlDriver>
  • WPFTreeView<TItemUserControlDriver>
<UserControl x:Class="Test.ActiveItemTestControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Test"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Canvas>
        <ListBox x:Name="_listBox" SelectionMode="Multiple" Height="148" Width="312">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Name}" />
                    <TextBox Text="{Binding Age}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Canvas>
</UserControl> 
public class ItemDriver : IAppVarOwner
{
    public AppVar AppVar { get; }
    public WPFTextBox Name => AppVar.VisualTree().ByBinding("Name").Dynamic();
    public WPFTextBox Age => AppVar.VisualTree().ByBinding("Age").Dynamic();
    public ItemDriver(AppVar appVar) => AppVar = appVar;
}

public void Test()
{
    var list = new WPFListBox<ItemDriver>(_ctrl._listBox);
    var item = list.GetItemDriver(1);
    item.Name.EmulateChangeTExt("Ishikawa");
    item.Name.EmulateChangeTExt("40");
}

The control driver is implemented using processing that uses the basic functions of Friendly.
If you are using non-standard controls such as 3rd party controls you will need to create a new one.
Knowledge of Friendly and its controls should not be so difficult.
When you make ControlDriver, it is better not to refer to the implementation of WPFStandard Controls.
It is difficult to read because there are many special writing methods that include support for .Net 4.0 and earlier.
Normally, it is not necessary to support .Net 4.0 or earlier, so it is better to write it differently.
Please refer to this as it is relatively easy to read.
It is 3rd party control driver.
https://github.com/Codeer-Software/Friendly.XamControls


Search Element

If you have an x:name, it is better to get it from the field using Friendly's basic functions.
It is recommended to add x:name during development to simplify the test.
If x:name does not exist, we have prepared the following means.

LogicalTree() and VisualTree()

var app = new WindowsAppFriend(process);
var mainWindow = app.WaitForIdentifyFromTypeFullName("TargetApp.MainWindow");

//wrote the type for clarity, but usually write it as var.
IWPFDependencyObjectCollection<DependencyObject> logicalTree = mainWindow.LogicalTree();
IWPFDependencyObjectCollection<DependencyObject> visualTree = mainWindow.VisualTree();

Extension methods for AppVar and IAppVarOwner.
Enabled with using RM.Friendly.WPFStandardCotnrols.
Returns a LogicalTree and a VisualTree as collections, respectively.
However, this is not a pure collection because the process is separated.
You cannot use the regular Linq method.
Normally, they are collected in the descendant direction, but they can also be collected in the ancestor direction by using an argument.

var logicalTree = mainWindow.LogicalTree(TreeRunDirection.Ancestors);

The following extension methods are provided for the returned IWPFDependencyObjectCollection.
Please Identify after narrowing down.

var logicalTree = mainWindow.LogicalTree();

IWPFDependencyObjectCollection<DependencyObject> selectedByBinding = logicalTree.ByBinding("Memo");
IWPFDependencyObjectCollection<DependencyObject> selectedByType = logicalTree.ByType("TargetApp.CustomTextBox");
IWPFDependencyObjectCollection<Button> selectedByType2 = logicalTree.ByType<Button>();

//Searches can be repeated.
IWPFDependencyObjectCollection<Button> selectedByTypeAndBinding = logicalTree.ByType<TextBox>().ByBinding("Name");

//Identify.
WPFTextBox memo = selectedByBinding.Single().Dynamic();
WPFTextBox name = selectedByTypeAndBinding.SingleOrDefault().Dynamic();
WPFButtonBase button1 = selectedByType2[1].Dynamic();

When ByType is narrowed down to the following types, search is prepared at each extension method.
IWPFDependencyObjectCollection<ContentControl>
IWPFDependencyObjectCollection<ButtonBase>

var logicalTree = mainWindow.LogicalTree();
IWPFDependencyObjectCollection<Button> selectedByType = logicalTree.ByType<Button>();

//Button can search both ContentControl and ButtonBase.
var buttonClose = main.LogicalTree().ByType<Button>().ByCommand(ApplicationCommands.Close).Single();
//If can't use  the type, you can also search by string.
var buttonClose = main.LogicalTree().ByType<Button>().ByCommand("System.Windows.Input.ApplicationCommands", "Close").Single();

//Command parameter.
var buttonA = main.LogicalTree().ByType<Button>().ByCommandParameter("A").Single();

//Content text.
var buttonA = main.LogicalTree().ByType<Button>().ByContentText("A").Single();

In target process (dll injection)

When Dll injection is performed, the above search can be executed inside the target process.
LogicalTree() and VisualTree() used here are extension methods for DependencyObject and return IEnumerable<DependencyObject>.
So you can use Linq and search more freely.

public static IEnumerable<DependencyObject> VisualTree(this DependencyObject start, TreeRunDirection direction = TreeRunDirection.Descendants);
public static IEnumerable<DependencyObject> LogicalTree(this DependencyObject start, TreeRunDirection direction = TreeRunDirection.Descendants);
public void Test()
{
    WPFStandardControls_3_5.Injection(_app);
    WindowsAppExpander.LoadAssembly(_app, GetType().Assembly);

    //get layout.
    var layout = _app.Type(GetType()).GetLayout(_app.Type<Application>().Current.MainWindow);
    var textBox = new WPFTextBox(layout.TextBox);
    var textBlock = new WPFTextBlock(layout.TextBlock);
    var button = new WPFButtonBase(layout.Button);
    var listBox = new WPFListBox(layout.ListBox);
}

class Layout
{
    public TextBox TextBox { get; set; }
    public TextBlock TextBlock { get; set; }
    public Button Button { get; set; }
    public ListBox ListBox { get; set; }
}

static Layout GetLayout(Window main)
{
    var logicalTree = main.LogicalTree();
    return new Layout()
    {
        TextBox = logicalTree.ByBinding("Memo").ByType<TextBox>().Single(),
        TextBlock = logicalTree.ByBinding("Memo").ByType<TextBlock>().Single(),
        Button = logicalTree.ByType<Button>().Where(x => x.IsCancel).Single(),
        ListBox = logicalTree.ByBinding("Persons").Single()
    };
}

For other GUI types, use the following libraries:


Breaking change about IWPFDependencyObjectCollection.SingleOrDefault() at 1.41.0

Count Before After
0 AppVar having null null
1 < Count AppVar having null throw exception

Releases

No releases published

Packages

No packages published

Languages

  • C# 100.0%