I need some help to focus a particular control when a TabPage is revisited. I followed many other blogs, but I wasn't able to solve the problem myself.
I created the TabPages inside a MDIForm:
Public Sub Tab_Open(Of T As {Form, New})(name As String, NameofTab As String, Tabnumber As String)
Dim _formByName As New Dictionary(Of String, Form)
Dim Frm As Form = Nothing
If Not _formByName.TryGetValue(name, Frm) OrElse _formByName(name).IsDisposed Then
Frm = New T()
_formByName(name) = Frm
End If
Dim childTab As TabPage = New TabPage With {
.Name = NameofTab & " : " & Tabnumber,
.Text = NameofTab & " : " & Tabnumber,
.Tag = Frm.Name
}
Form1.tabForms.TabPages.Add(childTab)
Frm.TopLevel = False
Frm.FormBorderStyle = FormBorderStyle.None
Frm.Parent = Form1.tabForms.TabPages(Form1.tabForms.TabCount - 1)
Frm.Dock = DockStyle.Fill
Frm.Show()
Form1.tabForms.SelectedTab = childTab
Form1.tabForms.Visible = True
End Sub
Let's assume that in first TabPage the Focus was on a TextBox (with TabIndex = 4), now I may be click on the second TabPage.
After some calculations, when I select the previous TabPage, the Focus should be set to the TextBox with TabIndex = 4 again, but that's not happening.
I tried to create a Dictionary in the MDIForm as:
Public Tab_Last_Focus_info As New Dictionary(Of String, String())
and in SelectedIndexChanged I have this code:
Private Sub tabForms_SelectedIndexChanged(sender As Object, e As EventArgs) Handles tabForms.SelectedIndexChanged
If Tab_Last_Focus_info.ContainsKey(tabForms.SelectedTab.Name) Then
Dim FullTypeName1 As String = String.Format("{0}", Tab_Last_Focus_info.Item(tabForms.SelectedTab.Name))
Dim Indxval As String = String.Format("{1}", Tab_Last_Focus_info.Item(tabForms.SelectedTab.Name))
Dim FullTypeName As String = Application.ProductName & "." & FullTypeName1
Dim FormInstanceType As Type = Type.GetType(FullTypeName, True, True)
Dim frm As Form = CType(Activator.CreateInstance(FormInstanceType), Form)
Dim Focus_on As Integer = Integer.Parse(Indxval)
frm.Controls(Focus_on).Focus()
' Not working too =>
' frm.Controls(Focus_on).Select()
' Invisible or disabled control cannot be activated =>
' ActiveControl = frm.Controls(Focus_on) 'System.ArgumentException:
End If
End Sub
In the Form, which is opened via a Menu, I have this code for the Control that's focused:
Private Sub All_Got_Focus(sender As Object, e As EventArgs) Handles TB_ImageLoc.GotFocus, TB_CompWebsite.GotFocus,
TB_CompPinCD.GotFocus, TB_CompPAN.GotFocus, TB_CompName.GotFocus, TB_CompMobile.GotFocus,
TB_CompMD.GotFocus, TB_CompLL.GotFocus, TB_CompGSTIN.GotFocus, TB_CompFax.GotFocus, TB_CompEmail.GotFocus,
TB_CompCD.GotFocus, TB_CompAreaCity.GotFocus, RTB_CompADD.GotFocus, PB_Logo.GotFocus, DTP_CompEst.GotFocus, DGV_CompList.GotFocus,
CHKB_CompIsRegTrans.GotFocus, CB_CompStateID.GotFocus, CB_CompDistrictID.GotFocus, But_Upd.GotFocus, But_SelectLogo.GotFocus,
But_Search.GotFocus, But_Reset.GotFocus, But_Refresh.GotFocus, But_GridSelect.GotFocus, But_Exit.GotFocus, But_Edit.GotFocus,
But_Del.GotFocus, But_Add.GotFocus
If Form1.Tab_Last_Focus_info.ContainsKey(Form1.tabForms.SelectedTab.Name) Then
Form1.Tab_Last_Focus_info.Remove(Form1.tabForms.SelectedTab.Name)
End If
Form1.Tab_Last_Focus_info.Add(Form1.tabForms.SelectedTab.Name, New String() {Me.Name, Me.ActiveControl.TabIndex})
End Sub
Now in TabIndexChange I'm getting a correct value from the Dictionary, but I'm not able to focus on the required tab.
Kindly help and let me know what I am missing or what need to taken care for this issue or please let me know any other better idea for the same.
First thing, a suggestion: test this code in a clean Project, where you have a MDIParent and one Form with a TabControl with 2 o more TabPages, containing different types of Controls. Test the functionality, then apply to the Project that is meant to use it.
You need to keep track of the selected Control in a TabPage - the current ActiveControl - switch to other TabPages, restore the previous ActiveControl in a TabPage when it's brought to front again.
The procedure is simple, implemented as follows:
To keep track of the current ActiveControl - the Control that has the Focus, you need to know when a Control becomes the ActiveControl. This Control of course must be child of a TabPage.
The ContainerControl class (the class from which Form derives) has a protected virtual method, UpdateDefaultButton(), that's overridden in the Form class. It's used to determine which child Button is activated when a User presses the Enter Key.
This method is called each time a new Control becomes the ActiveControl: overriding it, we can be informed when this happens, so we can check whether the new ActiveControl is one we're interested in, because it's child of a TabPage of our TabControl.
When the new ActiveControl is one we need to keep track of, we can store the reference of this Control and the Index of the TabPage it belongs to in a collection, so we can then use this reference, when the selected TabBage changes, to set it again as the ActiveControl in its TabPage.
Here, to store the state, I'm using a Dictionary(Of Integer, Control), where the Key is the Index of the TabPage and the Value is the reference of its ActiveControl.
When the TabControl.Selected event is raised - after a TabPage has been selected - we can lookup the Dictionary and restore the previous ActiveControl of that TabPage if one was stored.
► Here, BeginInvoke() is used to defer the action of setting the new ActiveControl, because this also causes a call to UpdateDefaultButton() and this method is called before the TabControl.Selected event handler completes.
Public Class SomeMdiChildForm
Private tabPagesActiveControl As New Dictionary(Of Integer, Control)()
' This method is called each time a Control becomes the ActiveControl
Protected Overrides Sub UpdateDefaultButton()
MyBase.UpdateDefaultButton()
If TypeOf ActiveControl.Parent Is TabPage Then
Dim tabPageIdx = CType(CType(ActiveControl.Parent, TabPage).Parent, TabControl).SelectedIndex
If tabPagesActiveControl.Count > 0 AndAlso tabPagesActiveControl.ContainsKey(tabPageIdx) Then
tabPagesActiveControl(tabPageIdx) = ActiveControl
Else
tabPagesActiveControl.Add(tabPageIdx, ActiveControl)
End If
End If
End Sub
Private Sub TabControl1_Selected(sender As Object, e As TabControlEventArgs) Handles TabControl1.Selected
Dim ctrl As Control = Nothing
If tabPagesActiveControl.TryGetValue(e.TabPageIndex, ctrl) Then
BeginInvoke(New Action(Sub() Me.ActiveControl = ctrl))
End If
End Sub
End Class
C# Version:
(assume tabControl1 is the name of the TabControl instance)
public partial class SomeForm : Form
{
private Dictionary<int, Control> tabPagesActiveControl = new Dictionary<int, Control>();
// [...]
// This method is called each time a Control becomes the ActiveControl
protected override void UpdateDefaultButton()
{
base.UpdateDefaultButton();
if (ActiveControl.Parent is TabPage tp) {
var tabPageIdx = (tp.Parent as TabControl).SelectedIndex;
if (tabPagesActiveControl.Count > 0 && tabPagesActiveControl.ContainsKey(tabPageIdx)) {
tabPagesActiveControl[tabPageIdx] = ActiveControl;
}
else {
tabPagesActiveControl.Add(tabPageIdx, ActiveControl);
}
}
}
private void tabControl1_Selected(object sender, TabControlEventArgs e)
{
if (tabPagesActiveControl.TryGetValue(e.TabPageIndex, out Control ctrl)) {
BeginInvoke(new Action(() => ActiveControl = ctrl));
}
}
}
As mentioned previously Tab_Open sub is used to create a form as tab.
In Main form (MDI) created Dictionary as
Public tabPagesActiveControl As New Dictionary(Of String, Integer)
In each form when the control is focused the value has been added to dictionary as
Private Sub DateTimePicker1_Leave(sender As Object, e As EventArgs) Handles RadioButton1.GotFocus,
DateTimePicker1.GotFocus, ComboBox1.GotFocus, CheckBox1.GotFocus, Button1.GotFocus, TextBox3.GotFocus, TextBox4.GotFocus, RichTextBox1.GotFocus
If Form1.tabPagesActiveControl.ContainsKey(Form1.TabControl1.SelectedTab.Name) Then
Form1.tabPagesActiveControl(Form1.TabControl1.SelectedTab.Name) = Me.ActiveControl.TabIndex
Else
Form1.tabPagesActiveControl.Add(Form1.TabControl1.SelectedTab.Name, Me.ActiveControl.TabIndex)
End If
End Sub
And when the tab is focused:
Private Sub TabControl1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles TabControl1.SelectedIndexChanged
If tabPagesActiveControl.ContainsKey(Me.TabControl1.SelectedTab.Name) Then
Dim Indxval As String = String.Format(tabPagesActiveControl.Item(Me.TabControl1.SelectedTab.Name))
SendKeys.Send("{TAB " & Indxval & "}")
End If
End Sub
As mentioned in the comments it has flaws. Kindly please check and help or do let me know what can be tried.
Finally I solved the issue after struggling for 8 Days :)
As I mentioned earlier I Open the forms as tabs using the Sub Tab_Open mentioned in the question.
Defined or created a new dictionary in MDI form as
Public tabPagesActiveControl As New Dictionary(Of String, Control)
and defined a control variable as
Dim Sel_Control As Control
Now in each form when the control is focused I have the below code to assign the current control alone to the dictionary:
Private Sub All_Focus(sender As Object, e As EventArgs) Handles TBox_Reg_website.GotFocus,
TBox_Reg_To.GotFocus, TBox_Reg_State.GotFocus, TBox_Reg_PinCD.GotFocus, TBox_Reg_PAN.GotFocus, TBox_Reg_office_num.GotFocus,
TBox_Reg_mobile_num.GotFocus, TBox_Reg_GSTIN.GotFocus, TBox_Reg_fax_no.GotFocus, TBox_Reg_email.GotFocus, TBox_Reg_country.GotFocus,
TBox_Reg_Company.GotFocus, TBox_Reg_City.GotFocus, TBox_Reg_Add2.GotFocus, TBox_Reg_Add1.GotFocus, TB_Curr_website.GotFocus,
TB_Curr_state.GotFocus, TB_Curr_RegTo.GotFocus, TB_Curr_Pincd.GotFocus, TB_Curr_Pan.GotFocus, TB_Curr_office_num.GotFocus,
TB_Curr_Mobile_num.GotFocus, TB_Curr_Gstin.GotFocus, TB_Curr_fax_no.GotFocus, TB_Curr_email.GotFocus, TB_Curr_country.GotFocus,
TB_Curr_Company.GotFocus, TB_Curr_city.GotFocus, TB_Curr_add2.GotFocus, TB_Curr_add1.GotFocus,
PICBox_Reg_Logo.GotFocus, MSP_Reg.GotFocus, Label9.GotFocus, Label8.GotFocus, Label7.GotFocus, Label6.GotFocus, Label5.GotFocus,
Label4.GotFocus, Label3.GotFocus, Label2.GotFocus, Label15.GotFocus, Label14.GotFocus, Label13.GotFocus, Label12.GotFocus,
Label11.GotFocus, Label10.GotFocus, Label1.GotFocus,
ChkBx_Upd_Logo.GotFocus, Chkbox_NoLogo.GotFocus
If Form1.tabPagesActiveControl.ContainsKey(Form1.TabControl1.SelectedTab.Name) Then
Form1.tabPagesActiveControl.Remove(Form1.TabControl1.SelectedTab.Name)
End If
Form1.tabPagesActiveControl.Add(Form1.TabControl1.SelectedTab.Name, Me.ActiveControl)
End Sub
and in the MDI form when tab select index changes having the below code:
Private Sub TabControl1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles TabControl1.SelectedIndexChanged
If tabPagesActiveControl.ContainsKey(Me.TabControl1.SelectedTab.Name) Then
Sel_Control = tabPagesActiveControl.Item(Me.TabControl1.SelectedTab.Name)
Sel_Control.Focus()
End If
End Sub
Thanks :)
I am developing a UWP app where there is a "page" and in that page, there are several "textbox"es which values are entered by a user (numbers). When it is navigated to other pages to take other values and came back to this page the values entered in "textbox"es are all lost. I used "NavigationCacheMode", no success.
Could you please help how to maintain values in UI elements (textboxes) in current page when navigating to other pages.
I would appreciate if the solution were in VB.NET.
Thank you very much.
You would have to save the date by yourself in this scenario. For example, you could choose the ApplicationData.LocalSettings to save the data when navigating to other page.
You could save the data on the page's OnNavigatedFrom method and get the data in its OnNavigatedTo method like the following:
<StackPanel>
<TextBox x:Name="textbox1"></TextBox>
<TextBox x:Name="textbox2"></TextBox>
<Button Content="navigate" Click="Button_Click"></Button>
</StackPanel>
Public NotInheritable Class MainPage
Inherits Page
Dim localSettings As Windows.Storage.ApplicationDataContainer = Windows.Storage.ApplicationData.Current.LocalSettings
Protected Overrides Sub OnNavigatedTo(e As NavigationEventArgs)
MyBase.OnNavigatedTo(e)
Dim value1 As Object = localSettings.Values(textbox1.Name)
Dim value2 As Object = localSettings.Values(textbox2.Name)
If value1 IsNot Nothing And value2 IsNot Nothing Then
textbox1.Text = value1.ToString()
textbox2.Text = value2.ToString()
End If
End Sub
Protected Overrides Sub OnNavigatedFrom(e As NavigationEventArgs)
MyBase.OnNavigatedFrom(e)
localSettings.Values(textbox1.Name) = textbox1.Text
localSettings.Values(textbox2.Name) = textbox2.Text
End Sub
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Frame.Navigate(GetType(BlankPage1))
End Sub
End Class
In my Windows Phone 8 app, I have a ListBox control. This list box contains a grid, which contains a pair of TextBlock (title of the field) and a TextBox (user input) controls. This list is generated based on the results of a service the app connects to. What I need to do is access each textbox within the list, and bring back the value of it. To do this, I have bound a unique ID of each item to the Tag property of the TextBox, and I am using the LostFocus event to capture the user input. Once this has been captured and added to a collection in the code behind, the data is processed when the user clicks a button under the list. This works fine for every item except the last one.
The problem is that the LostFocus doesn't work if the button is clicked. The button click method seems to take precedence over the textbox LostFocus method. So if there is only 1 item in the list, the value isn't recorded. Here is the code:
<ItemsControl x:Name="itmScreen4">
<TextBlock Margin="0,10,0,10">Fields</TextBlock>
<ListBox x:Name="lstCustom" ItemsSource="{Binding}" Visibility="Visible">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="grdCustom">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="txtCustTitle" Text="{Binding Name}" Foreground="#4C4C4C" FontSize="18" Margin="0,15,0,0"></TextBlock>
<TextBox Tag="{Binding DataID}" x:Name="txtCust" Grid.Row="1" Style="{StaticResource TextBox}" Width="450" LostFocus="txtCust_LostFocus"></TextBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="btnSubmit" Content="Submit" Background="#404040"></Button>
</ItemsControl>
For the final item in the list (or if there's only one item in the list), the txtCust_LostFocus method doesn't get called when the btnSubmit method is called. Any ideas of how I can capture that final textbox value?
I have tried some other methods (casting a ListBoxItem and performing a FindName on it, for example), but haven't found anything that works. Google hasn't been much help either. Thanks in advance.
EDIT:
Here is the code behind. I am binding a custom class to the list as below.
Class definition here (I have removed some properties for readabality purposes):
Public Class CustomDataRequest
Public Sub New()
End Sub
Public Property ID As Integer
Public Property Name As String
End Class
Use in the code here:
Public Sub ShowCustomData()
Dim CustomDataList As New List(Of CustomDataRequest)()
For Each item In _CustomDataRequestList
If item.ID= _CurrentID Then
CustomDataList.Add(item)
End If
Next
lstCustom.ItemsSource = CustomDataList.ToArray
End Sub
The txtCust_LostFocus method is just capturing the fields at the minute. Once I can actually get it called, I can then add the data to the collection:
Private Sub txtCust_LostFocus(sender As Object, e As RoutedEventArgs)
Dim elem = DirectCast(sender, FrameworkElement)
Dim txt = DirectCast(elem.FindName("txtCust"), TextBox)
Dim text As String = txt.Text
Dim tag As String = txt.Tag
End Sub
The problem is that it never gets called once the button has been tapped:
Protected Sub btnSubmit_Tap(sender As Object, e As Input.GestureEventArgs) Handles btnSubmit.Tap
ValidateData()
End Sub
Here is the answer to this. The TextBox should have TwoWay binding mode set, to bind the text value to a corresponding Value property in the class. It should also have UpdateSourceTrigger set to Explicit for binding efficiency, as detailed here.
Public Class CustomDataRequest
Public Sub New()
End Sub
Public Property ID As Integer
Public Property Name As String
'New property here
Public Property Value As String
End Class
Textbox code:
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=Explicit}" x:Name="txtCust" Grid.Row="1" BorderBrush="#BBBBBB" Style="{StaticResource TextBox}" Width="450" TextChanged="Textbox_Changed"></TextBox>
In the code behind, the following method should be called from the Textbox TextChanged (translated into VB):
Private Sub Textbox_Changed(sender As Object, e As TextChangedEventArgs)
Dim txt As TextBox = TryCast(sender, TextBox)
Dim bindingExpr As BindingExpression = txt.GetBindingExpression(TextBox.TextProperty)
bindingExpr.UpdateSource()
End Sub
This solves the problem of the textbox value not binding when the focus is still on the textbox. Thanks to venerik for the answer to this question.
I am trying to set the tool tip text for some of my subitems in my listview control. I am unable to get the tool tip to show up.
Anyone have any suggestions?
Private _timer As Timer
Private Sub Timer()
If _timer Is Nothing Then
_timer = New Timer
_timer.Interval = 500
AddHandler _timer.Tick, AddressOf TimerTick
_timer.Start()
End If
End Sub
Private Sub TimerTick(ByVal sender As Object, ByVal e As EventArgs)
_timer.Enabled = False
End Sub
Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
If Not _timer.Enabled Then
Dim item = Me.HitTest(e.X, e.Y)
If Not item Is Nothing AndAlso Not item.SubItem Is Nothing Then
If item.SubItem.Text = "" Then
Dim tip = New ToolTip
Dim p = item.SubItem.Bounds
tip.ToolTipTitle = "Status"
tip.ShowAlways = True
tip.Show("FOO", Me, e.X, e.Y, 1000)
_timer.Enabled = True
End If
End If
End If
MyBase.OnMouseMove(e)
End Sub
You can use the MouseMove event:
private void listview1_MouseMove(object sender, MouseEventargs e)
{
ListViewItem item = listview1.GetItemAt(e.X, e.Y);
ListViewHitTestInfo info = listview1.HitTest(e.X, e.Y);
if((item != null) && (info.SubItem != null))
{
toolTip1.SetToolTip(listview1, info.SubItem.Text);
}
else
{
toolTip1.SetToolTip(listview1, "");
}
}
Assuming .NET 2.0 or later, you can also set ListView.ShowItemToolTips to true. If you need to customize the tooltip text for a given item, set ListViewItem.ToolTipText to the string you want displayed.
ObjectListView (an open source wrapper around .NET WinForms ListView) has builtin support for cell tooltips (and, yes, it does work with VB). You listen for a CellToolTip event and you can do things like this (which is admittedly excessive):
If you don't want to use ObjectListView, you need to subclass ListView, listen for WM_NOTIFY messages, and then within those, respond to TTN_GETDISPINFO notifications, in a manner similar to this:
case TTN_GETDISPINFO:
ListViewHitTestInfo info = this.HitTest(this.PointToClient(Cursor.Position));
if (info.Item != null && info.SubItem != null) {
// Call some method of your own to get the tooltip you want
String tip = this.GetCellToolTip(info.Item, info.SubItem);
if (!String.IsNullOrEmpty(tip)) {
NativeMethods.TOOLTIPTEXT ttt = (NativeMethods.TOOLTIPTEXT)m.GetLParam(typeof(NativeMethods.TOOLTIPTEXT));
ttt.lpszText = tip;
if (this.RightToLeft == RightToLeft.Yes)
ttt.uFlags |= 4;
Marshal.StructureToPtr(ttt, m.LParam, false);
return; // do not do normal processing
}
}
break;
Obviously, this is C#, not VB, but you get the idea.
The original code in the question does not work since it creates a New ToolTip inside OnMouseMove. I guess that the ToolTip.Show method is asynchronous and thus the function exits immediately after invoking it, destroying the temporary ToolTip. When Show gets to execute, the object does not exist anymore.
The solution would be to create a persistent ToolTip object, by:
a ToolTip control on the form; or
a private ToolTip class field (disposed in the Finalize or Dispose method of the class); or
a Static object inside the function.
Also, there is no need to GetItemAt() since ListViewHitTestInfo already contains both the item and subitem references.
Improving Colin's answer, here is my code:
Private Sub ListView_MouseMove(sender As Object, e As MouseEventArgs) _
Handles MyList1.MouseMove
Static prevMousePos As Point = New Point(-1, -1)
Dim lv As ListView = TryCast(sender, ListView)
If lv Is Nothing Then _
Exit Sub
If prevMousePos = MousePosition Then _
Exit Sub ' to avoid annoying flickering
With lv.HitTest(lv.PointToClient(MousePosition))
If .SubItem IsNot Nothing AndAlso Not String.IsNullOrEmpty(.SubItem.Text) Then
'AndAlso .Item.SubItems.IndexOf(.SubItem) = 1
'...when a specific Column is needed
Static t As ToolTip = toolTip1 ' using a form's control
'Static t As New ToolTip() ' using a private variable
t.ShowAlways = True
t.UseFading = True
' To display at exact mouse position:
t.Show(.SubItem.Tag, .Item.ListView, _
.Item.ListView.PointToClient(MousePosition), 2000)
' To display beneath the list subitem:
t.Show(.SubItem.Tag, .Item.ListView, _
.SubItem.Bounds.Location + New Size(7, .SubItem.Bounds.Height + 1), 2000)
' To display beneath mouse cursor, as Windows does:
' (size is hardcoded in ugly manner because there is no easy way to find it)
t.Show(.SubItem.Tag, .Item.ListView, _
.Item.ListView.PointToClient(Cursor.Position + New Size(1, 20)), 2000)
End If
prevMousePos = MousePosition
End With
End Sub
I've made the code as general as possible so that the function could be assigned to multiple ListViews.
If you set ShowItemTooltips for the ListView control in "details" mode and do nothing else, the ListView control will automatically provide tooltips for items and subitems that exceed their column's widths. This turns out to work even if the FullRowSelect property is set to true. If ToolTipText has been set for a ListViewItem and FullRowSelect is true, then the tooltip will appear for the whole row; that's the case where tooltips won't be displayed for subitems.