Windows narrator is not reading the custom tooltip content? - wpf

I have a requirement to read the content of complex tooltip content for WPF or UWP application by narrator. I am facing challenge to read the content visible for tooltip. Have tried to override the AutomationPeer class and there methods. But no luck :(
My XAML UI is below:
<Button Content="Submit" Grid.Row="2" Height="100" Width="200" >
<Button.ToolTip x:Uid="Addition_Details" >
<local:MyStackPanel Orientation="Vertical" Focusable="True" KeyboardNavigation.TabIndex="0" ForceCursor="True">
<TextBlock Text="Additional Details" KeyboardNavigation.TabIndex="1"/>
<TextBlock Text="Driver" KeyboardNavigation.TabIndex="2"/>
<TextBlock Text="A0221" KeyboardNavigation.TabIndex="3"/>
</local:MyStackPanel>
</Button.ToolTip>
</Button>
CustomGrid class is like:
public class MyStackPanel:StackPanel
{
protected override AutomationPeer OnCreateAutomationPeer()
{
return new UIAutomationChildPeer(this);
}
}
public class UIAutomationChildPeer : FrameworkElementAutomationPeer
{
public UIAutomationChildPeer(FrameworkElement element):base(element)
{
}
protected override string GetClassNameCore()
{
return "Additional Details";
}
protected override List<AutomationPeer> GetChildrenCore()
{
var childrenAutomationPeer = new List<AutomationPeer>();
var owner = Owner as StackPanel;
if (owner != null)
{
//owner.GotFocus += Owner_GotFocus;
var childElements = owner.Children;// indName("myGrid", owner) as Grid;
if (childElements != null && childElements.Count > 0)
{
foreach (TextBlock item in childElements)
{
var headerTextBlockAutomationPeer = new TextAutomationPeer(item);
childrenAutomationPeer.Add(headerTextBlockAutomationPeer);
}
}
}
return childrenAutomationPeer;
}
}
public class TextAutomationPeer : TextBlockAutomationPeer
{
private StringBuilder detail = new StringBuilder();
public UIElement Element { get { return Owner; } }
public TextAutomationPeer(TextBlock owner) : base(owner)
{
if (!string.IsNullOrWhiteSpace(owner.Text.ToString()))
{
detail.Append(owner.Text.ToString());
}
}
public override string ToString()
{
return detail.ToString();
}
}
Have tried with manually triggering the focus event or setting Tab Index. Nothing brings out the result. Any leads for resolving this issue.
#UWP #WPF #Windows10

The controls that you put in the tooltip won't get focused so the narrator won't respond to them. This is the same for TextBlock.
Generally, we will use the AutomationProperties.HelpText Attached Property to add simple text to a control. The narrator will respond to the AutomationProperties.HelpText.

Related

WPF MVVM Data Binding - One Way?

I am new to WPF but have an small understanding of MVVM, so far this is what I have implemented.
UpdateTableView - View (Short snippet of larger user control)
<UserContol.DataContext>
<local:UpdateTableViewModel />
</UserContol.DataContext>
<StackPanel>
<TextBox Text="{Binding InputPath}"/>
<TextBlock Content="Placeholder" />
</StackPanel>
UpdateTableModel - Model
public class UpdateTableModel : ObservableObject
{
private string _inputPath;
public string InputPath
{
get
{
return _inputPath;
}
set
{
if (value != _inputPath)
{
_inputPath = value;
OnPropertyChanged("InputPath");
}
}
}
}
ObservableObject
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanaged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanaged;
if (handler != null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
And an empty UpdateTableViewModel
class UpdateTableViewModel : ObservableObject { }
My question is how would I use data binding so that as a user when I enter a inputPath in the text box, firstly whatever I type is store in the property _inputPath so I can use it in code behind and additionally be reflected in the text block.
I have done some research and found about one way and two way data binding and can't really work out what else I need to add for my desired functionality.
Thanks in advance.
Your view models must contain the properties you want to bind to.
Generally the TextBox.Text property automatically binds TwoWay. This is the default behavior. So, without specifying the Binding.Mode explicitly, the text entered into the TextBox will be automatically sent to the binding source. In your case the input would be automatically sent to the InputPath property.
UpdateTableModel.cs
public class UpdateTableModel
{
public void SaveUserNameToFile(string filePath, string userName)
{
File.AppendAllText(filePath, userName, Encoding.UTF8);
}
}
UpdateTableViewModel.cs
An implementation of RelayCommand can be found at
Microsoft Docs: Patterns - WPF Apps With The Model-View-ViewModel Design Pattern - Relaying Command Logic
class UpdateTableViewModel : ObservableObject
{
private UpdateTableModel UpdateTableModel { get; }
public ICommand SaveUserCommand => new RelayCommand(SaveUserName);
private string _userName;
public string UserName
{
get => _userName;
set
{
if (value != _userName)
{
_userName = value;
OnPropertyChanged(nameof(UserName));
}
}
}
private string _inputPath;
public string InputPath
{
get => _inputPath;
set
{
if (value != _inputPath)
{
_inputPath = value;
OnPropertyChanged(nameof(InputPath));
}
}
}
public UpdateTableViewModel()
{
this.UpdateTableModel = new UpdateTableModel();
}
// Alternative constructor
public UpdateTableViewModel(UpdateTableModel updateTableModel)
{
this.UpdateTableModel = updateTableModel;
}
private void SaveUserName(object param)
{
// Pass the data to the model
this.UpdateTableModel.SaveUserNameToFile(this.InputPath, this.UserName);
}
}
UpdateTableView.xaml
<UserControl>
<UserContol.DataContext>
<local:UpdateTableViewModel />
</UserContol.DataContext>
<StackPanel>
<TextBox Text="{Binding UserName}" />
<TextBox Text="{Binding InputPath}" />
<Button Command="{Binding SaveUserCommand}"
Content="Save to File" />
</StackPanel>
</UserControl>

WPF: Changing tabs makes Windowsformshost child disappear

if you can spare the time, I am working on a problem for which I can't find a solution on the internet.
I need two tabs' richtextboxes to bind the same property. Both RichtextBoxes are hosted in WPF via Windowsformshost. But if I alternate between tabs, one RichtTextBox will simply dissapear (always the first one that was visible). I am migrating an app and so far, I am forced to use the Windowsforms RichtextBox.
I hope I managed to properly convey my problem - sorry, I am not a native speaker.
Thanks in advance
Edit:
I was asked to provide a clear example of my problem. Thanks for the note. I completely rewrote my question. Further, I have uploaded a micro app where I have isolated the problem. Just click the two tab buttons alternately and one Richtextbox will dissapear.
Below, I will provide the code if this serves:
This is my Mainwindow (XAML):
<StackPanel Orientation="Horizontal" Height="35" Margin="0,35,0,0" VerticalAlignment="Top" >
<Button x:Name="Tab1" Command="{Binding LeftCommand}" Content="Left" MinWidth="100" />
<Button x:Name="Tab2" Command="{Binding RightCommand}" Content="Right" MinWidth="100" />
</StackPanel>
<Frame x:Name="MyFrame"
Content="{Binding Path=CurrentTab, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="5,70,0,0" NavigationUIVisibility="Hidden" />
This is its viewmodel:
class MainWindowViewModel : INotifyPropertyChanged
{
public ICommand LeftCommand { get; }
public ICommand RightCommand { get; }
private TabViewModel MyTabViewModel { get; set; }
private PageLeft MyPageLeft { get; set; }
private PageRight MyPageRight { get; set; }
public MainWindowViewModel()
{
this.LeftCommand = new ModelCommand(p => this.SetSelectedTab("left"));
this.RightCommand = new ModelCommand(p => this.SetSelectedTab("right"));
this.MyTabViewModel = new TabViewModel();
this.MyPageLeft = new PageLeft() { DataContext = this.MyTabViewModel };
this.MyPageRight = new PageRight() { DataContext = this.MyTabViewModel };
//initial view on something
//this.SetSelectedTab("left");
}
private void SetSelectedTab(string param)
{
switch (param)
{
case "left":
this.CurrentTab = this.MyPageLeft;
break;
case "right":
this.CurrentTab = this.MyPageRight;
break;
}
}
private object _CurrentTab;
public object CurrentTab
{
get { return _CurrentTab; }
set
{
if (value != _CurrentTab)
{
_CurrentTab = value;
NotifyPropertyChanged_MainViewModel();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged_MainViewModel([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Furthermore, I have two pages (MyPageLeft, MyPageRight) that use the same viewmodel (TabViewModel) and use the same bit of XAML code:
<ContentControl Content="{Binding Path=MyWindowsFormsHost, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Both Pages use the same TabViewModel:
class TabViewModel : INotifyPropertyChanged
{
private WindowsFormsHost _MyWindowsFormsHost;
public WindowsFormsHost MyWindowsFormsHost
{
get { return _MyWindowsFormsHost; }
set
{
if (value != _MyWindowsFormsHost)
{
_MyWindowsFormsHost = value;
NotifyPropertyChanged_TabViewModel();
}
}
}
public TabViewModel()
{
this.MyWindowsFormsHost = new WindowsFormsHost() { Child = new RichTextBox() { Text = "test" } };
}
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged_TabViewModel([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The Problem: If I start the app and click on the two tab buttons alternatingly, one of the framed RichtextBoxes will dissapear.
If anyone might need it, I used a dirty solution - although it might not be recommendable.
I extended the event of the switch tab buttons. It takes the RTF property of the currently selected Tab's Richtextbox and infuses it in the other Richtextbox. It goes kinda like this:
if (Tab2 Button is clicked)
this.MyRTF = Tab1.Richtextbox.RTF;
Tab2.Richttextbox.Rtf = this.MyRTF;
Note that this is a beginner's hack on a probably overall questionable approach.
Thanks to anyone who read my question!

How to test ItemsControl with TestStack.White.UIItems

So I'm trying to test UI WPF application. I'm using TestStack.White framework for testing. UI has custom control DragDropItemsControl. This control inherits from ItemsControl. So how can I test this control.
<wpf:DragDropItemsControl x:Name="uiTabsMinimizedList"
Margin="0 0 0 5"
VerticalAlignment="Top"
AllowDropOnItem="False"
DragDropTemplate="{StaticResource TemplateForDrag}"
ItemDropped="uiTabsMinimizedList_ItemDropped"
ItemsSource="{Binding ElementName=uiMain,
Path=MinimizedTabs}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
TextBlock.Foreground="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl},
Path=Foreground}">
<wpf:DragDropItemsControl.ItemTemplate>
<DataTemplate>
<Border >
<TextBlock Cursor="Hand" Text="{Binding Panel.Label}" />
</Border>
</DataTemplate>
</wpf:DragDropItemsControl.ItemTemplate>
</wpf:DragDropItemsControl>
Can we test?
You have to create your own AutomationPeer for your DragDropItemsControl and for your custom control item then you will be able to define the AutomationId as an identifier of the your item object.
public class DragDropItemsControl : ItemsControl
{
protected override AutomationPeer OnCreateAutomationPeer()
{
return new DragDropItemsAutomationPeer(this);
}
}
The custom AutomationPeer class for your control.
public class DragDropItemsControlAutomationPeer : ItemsControlAutomationPeer
{
public DragDropItemsControlAutomationPeer(DragDropItemsControl owner)
: base(owner)
{
}
protected override string GetClassNameCore()
{
return "DragDropItemsControl";
}
protected override ItemAutomationPeer CreateItemAutomationPeer(object item)
{
return new DragDropItemsControlItemAutomationPeer(item, this);
}
}
The custom AutomationPeer class for your control items.
The important part here is the implementation of the method GetAutomationIdCore().
public class DragDropItemsControlItemAutomationPeer : ItemAutomationPeer
{
public DragDropItemsControlItemAutomationPeer(object item, ItemsControlAutomationPeer itemsControlAutomationPeer)
: base(item, itemsControlAutomationPeer)
{
}
protected override string GetClassNameCore()
{
return "DragDropItemsControl_Item";
}
protected override string GetAutomationIdCore()
{
return (base.Item as MyTestItemObject)?.ItemId;
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return base.GetAutomationControlType();
}
}
For the following xaml code
<local:MyItemsControl x:Name="icTodoList" AutomationProperties.AutomationId="TestItemsControl">
<local:MyItemsControl.ItemTemplate>
<DataTemplate>
<Border >
<TextBlock Cursor="Hand" Text="{Binding Title}" />
</Border>
</DataTemplate>
</local:MyItemsControl.ItemTemplate>
</local:MyItemsControl>
Init in code behind
public MyMainWindow()
{
InitializeComponent();
List<MyTestItemObject> items = new List<MyTestItemObject>();
items.Add(new MyTestItemObject() { Title = "Learning TestStack.White", ItemId="007" });
items.Add(new MyTestItemObject() { Title = "Improve my english", ItemId = "008" });
items.Add(new MyTestItemObject() { Title = "Work it out", ItemId = "009" });
icTodoList.ItemsSource = items;
}
public class MyTestItemObject
{
public string Title { get; set; }
public string ItemId { get; set; }
}
We can see in UIAVerify
Sample code to check the values
// retrieve the custom control
IUIItem theItemsControl = window.Get(SearchCriteria.ByAutomationId("008"));
if (theItemsControl is CustomUIItem)
{
// retrieve the custom control container
IUIItemContainer controlContainer = (theItemsControl as CustomUIItem).AsContainer();
// get the child components
WPFLabel theTextBlock = controlContainer.Get<WPFLabel>(SearchCriteria.Indexed(0));
// get the text value
string textValue = theTextBlock.Text;
}

How to change text on a Label after a button click, in MVVM Light

I have a MVVM Light WPF project that I'm working on.
I want to update the text via binding on the label when a button is clicked.
Not really sure how to do this within the view model.
Below is a look at my view code and view model code. Basically, I want to update the label with the ProjectStatus binding to say Project Created after the `Create New Project' button is clicked.
Any help would be appreciated.
Here is my code:
<Button Content="Create New Project" Margin="0,0,3,0" Command="{Binding AddProjectCommand}" Width="243"/>
<Label Margin="20,0,0,0" Content="{Binding ProjectStatus, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="325"/>
Here is the Model Code:
public RelayCommand AddProjectCommand { get; set; }
public ProjectConfigViewModel()
{
_projectStatus = "Project not created";
this.AddProjectCommand = new RelayCommand(() => AddProject());
}
public void AddProject()
{
DatabaseInteraction.CreateProjectDb(_projName);
isProjectLoaded = false;
}
public string ProjectStatus
{
get { return _projectStatus; }
set
{
if (value != _projectStatus)
{
_projectStatus = value;
RaisePropertyChanged("ProjectStatus");
}
}
}
Why not just like that?
public void AddProject()
{
DatabaseInteraction.CreateProjectDb(_projName);
isProjectLoaded = false;
ProjectStatus = "Project Created";
}

How to create an object that derivative from TextBox ?

I trying to create new class object that derivative from TextBox -
If there is chars in the TextBox - the new object will show some button and pressing on this button will be able to remove the chars in this TextBox
how can i make the derivative from control in WPF ?
You could create a new UserControl with a textbox and a button. You bind a string property to the textbox and to the visibility-property of your button. Then you create a converter which converts this string to a visibility. Now you bind the Command-property of your button to a command which sets the string property = string.Empty.
A few hints:
How to use Converters:
<UserControl.Resources>
<local:StringToVisibilityConverter x:Key="STV"></local:StringToVisibilityConverter>
</UserControl.Resources>
<Button Visibility="{Binding Path=MyText, Converter={StaticResource ResourceKey=STV}}" />
How your VM could look like:
public class MainViewModel:ViewModelBase
{
private string _mytext;
public string MyText
{
get
{
return _mytext;
}
set
{
_mytext = value;
OnPropertyChanged("MyText");
}
}
private RelayCommand<object> _clearTextCommand;
public ICommand ClearTextCommand
{
get
{
if (_clearTextCommand == null)
{
_clearTextCommand = new RelayCommand<object>(o => ClearText(), o => CanClearText());
}
return _clearTextCommand;
}
}
private void ClearText()
{
MyText = string.Empty;
}
private bool CanClearText()
{
return true;
}
}

Resources