Set ComboBox cue banner in Windows XP - combobox

I'm updating a VB.net project that needs to have cue banners added to text boxes and read-only comboboxes (DropDownStyle=DropDownList). The machine I'm developing on is Windows 7. I'm adding the cue text in a class that extends a combobox and adds a cue text property. This is how the cue text is added to the combobox:
'"Me" refers to a combobox that has been extended to include a Cue Text property
SendMessage(New HandleRef(Me, Me.Handle), CB_SETCUEBANNER, IntPtr.Zero, _cueText)
The above code is from this blog bost: http://www.aaronlerch.com/blog/page/7/, which is in C#; I translated it to VB. I tried other similar variations that I found elsewhere, all with the same result: it works great for text boxes and comboboxes when I run the program in Windows 7; it only works for text boxes in Windows XP.
I've read lots of comments on different forums about making sure visual styles are selected and disabling east Asia languages and complex scripts. I've done all that, but still haven't gotten it to work on XP.
Has anyone gotten cue banners for comboboxes to work on XP?

Using various blog and forum posts, I created a class that extends the ComboBox control and implements a CueText property that works on Windows 7 and XP. I found the most relevant pieces of information here:
How to set cue text in XP: http://www.ageektrapped.com/blog/the-missing-net-1-cue-banners-in-windows-forms-em_setcuebanner-text-prompt/
How to set cue text in Windows 7: http://www.aaronlerch.com/blog/2007/12/01/watermarked-edit-controls/
In a nutshell, Windows 7 and XP set the cue banner text slightly differently, so you need to check which operating system the program is running on and then handle the cue text appropriately. You need to use EM_SETCUEBANNER As Integer = &H1501 for XP and CB_SETCUEBANNER As UInteger = &H1703 for Windows 7. You also need to single out the text part of the combo box if the app is running on XP. You can see the details in the code below. To figure out which OS is running, see MS KB articles 304289 (VB) or 304283 (C#). (I would post the links, but I don't have enough reputation points to post more than two.)
One caveat is that this will not work on XP if the combo boxes are read only (DropDownStyle = DropDownList). Windows 7 seems to work fine either way. If your application needs to run on XP and you need the combo boxes to be read only but still display the cue text, here's what you can do:
Create a combo box and use the default DropDownStyle, "DropDown"
Handle the KeyPress event for the combo box(es) and in that method tell it that the event has been handled like this: e.Handled = True. This will prevent text from being typed.
Create a blank ContextMenuStrip using the Toolbox in the design view, click on the combo box, view its properties, and set its ContextMenuStrip to the one you just created. This will cause nothing to happen when the user right clicks in the combo box so they can't paste text into it.
Here's the VB code for a class that inherits the ComboBox control and adds a CueText property that works for XP and 7. The only thing you'll have to do is figure out which OS is running:
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Public Class CueComboBox
Inherits ComboBox
' Occurs when the CueText property value changes.
Public Event CueTextChanged As EventHandler
'Windows XP
Private Shared EM_SETCUEBANNER As Integer = &H1501
'Windows 7
Private Shared CB_SETCUEBANNER As UInteger = &H1703
<DllImport("user32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As Int32
End Function
<DllImport("user32.dll")> _
Private Shared Function GetComboBoxInfo(ByVal hwnd As IntPtr, ByRef pcbi As COMBOBOXINFO) As Boolean
End Function
<StructLayout(LayoutKind.Sequential)> _
Private Structure COMBOBOXINFO
Public cbSize As Integer
Public rcItem As RECT
Public rcButton As RECT
Public stateButton As IntPtr
Public hwndCombo As IntPtr
Public hwndItem As IntPtr
Public hwndList As IntPtr
End Structure
<StructLayout(LayoutKind.Sequential)> _
Private Structure RECT
Public left As Integer
Public top As Integer
Public right As Integer
Public bottom As Integer
End Structure
Private Shared Function GetComboBoxInfo(ByVal control As Control) As COMBOBOXINFO
Dim info As New COMBOBOXINFO()
'a combobox is made up of three controls, a button, a list and textbox;
'we want the textbox
info.cbSize = Marshal.SizeOf(info)
GetComboBoxInfo(control.Handle, info)
Return info
End Function
Private _cueText As String = [String].Empty
' Gets or sets the text that will display as a cue to the user.
<Description("The text value to be displayed as a cue to the user.")> _
<Category("Appearance")> <DefaultValue("")> <Localizable(True)> _
Public Property CueText() As String
Get
Return _cueText
End Get
Set(ByVal value As String)
If value Is Nothing Then
value = [String].Empty
End If
If Not _cueText.Equals(value, StringComparison.CurrentCulture) Then
_cueText = value
UpdateCue()
OnCueTextChanged(EventArgs.Empty)
End If
End Set
End Property
<EditorBrowsable(EditorBrowsableState.Advanced)> _
Protected Overridable Sub OnCueTextChanged(ByVal e As EventArgs)
RaiseEvent CueTextChanged(Me, e)
End Sub
Protected Overrides Sub OnHandleCreated(ByVal e As EventArgs)
UpdateCue()
MyBase.OnHandleCreated(e)
End Sub
Private Sub UpdateCue()
' If the handle isn't yet created, this will be called when it is created
If Me.IsHandleCreated Then
' Windows XP sets the cue banner differently than Windows 7
If Form1.OPERATING_SYSTEM = "Windows XP" Then
Dim info As COMBOBOXINFO = GetComboBoxInfo(Me)
SendMessage(info.hwndItem, EM_SETCUEBANNER, 0, _cueText)
Else
SendMessage(New HandleRef(Me, Me.Handle), CB_SETCUEBANNER, IntPtr.Zero, _cueText)
End If
End If
End Sub
End Class

Related

Use the same WPF window for two different purposes

I have a WPF application written in VB with several windows. These windows have a several controls in them for the users to enter data. This data is then saved to a database. I want the users to be a able to edit a given set of data and it would be a lot more convenient to use the same window the data was entered in. Depending on whether the user clicks "Add" or "Edit", I want to run different code behind the window.
My issue is that I can't figure out how to differentiate between these two events. The MainWindow class has buttons "Add" and "Edit". When clicked, they create a new tab that contains a new instance of "Data.xaml". "Data.xaml" has "Data.vb" behind it. How can "Data.vb" tell whether it should execute "Edit" or "Add" code?
Simple solution is to add some property to the Data class which will tell what should be done:
Public Partial Class Data Inherits Window
// ...
Public Property Mode As Mode
// ...
End Class
where Mode is enum with two fields: Add and Edit.
In click handler of Add button set Mode to Mode.Add, in click handler of Edit button set Mode to Mode.Edit.
If you want to prevent changing of Mode after window constructed you can create new constructor that will take mode as an argument:
Public Partial Class Data Inherits Window
// ...
Public Sub New(mode As Mode)
Me.New()
Mode = mode
End Sub
Public ReadOnly Property Mode As Mode
// ...
End Class
Then in your logic in Data.vb look at the Mode and do appropriate actions.
Private Sub AddButton_Click(sender As Object, e As RoutedEventArgs)
Dim dataWindow = New Data(Mode.Add)
dataWindow.ShowDialog()
End Sub
Private Sub EditButton_Click(sender As Object, e As RoutedEventArgs)
Dim dataWindow = New Data(Mode.Edit)
dataWindow.ShowDialog()
End Sub

How to use this style on different textboxes with different text in WPF / VB.NET

This answer shows how to use a cuebanner with a textbox. The content for the label is for each textbox the same. A comment on that answer states that you can use attached properties to increase style re-usability and includes a link to C# code.
I'm able to write/read C# but unfortunately I don't understand what the code does. I also have done some searching for attached properties but I really don't get how I can change the displayed text so it reusable.
Could anyone give me an example how I do this in VB.NET or link me some sites that explain how it's done?
This will give you watermark text on your textbox controls:
Imports:
Imports System.Runtime.InteropServices
Global Declarations in your main class:
Private Const EM_SETCUEBANNER As Integer = &H1501
<DllImport("user32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As Int32
End Function
Functions in your main class:
Private Sub SetCueText(ByVal control As Control, ByVal text As String)
SendMessage(control.Handle, EM_SETCUEBANNER, 0, text)
End Sub
Usage (usually in form_load event):
SetCueText(TextBox1, "blah")
SetCueText(TextBox2, "blahblah")
Is this what you meant?
Hope this helps :)

I can't use Window.Show despite of creating new Window instance

here's the problem:
In my WPF application I used to load/parse my .xaml files using XamlReader.Load Method to open a window in my application.
Codefragment of my function which return the window:
Dim win As New Window()
Dim myObject As Object
Dim xml As XmlReader = XmlReader.Create("mysample.xaml")
myObject = System.Windows.Markup.XamlReader.Load(xml)
win = CType(myObject, Window)
Return win
I use this to display all my different windows the user wants to see.
I open the window with win.Show and close it, when user switch to another window with win.Close. It works well!
Now to increase the performance I plan to do all the XAMLReader.Load at Application Start and store the information into a Dictionary:
Private Shared windict As Dictionary(Of String, Object)
Public Shared Sub ConvertXAMLToWindow(ByVal formName As String)
windict = New Dictionary(Of String, Object)
Dim myObject As Object
Dim xml As XmlReader = XmlReader.Create(formName)
myObject = System.Windows.Markup.XamlReader.Load(xml)
windict.Add(formName, myObject)
End Sub
Then I want to use that information when calling windows:
If windict.ContainsKey(formName) Then
Dim win As New Window()
Dim myObject As Object
myObject = windict(formName)
win = CType(myObject, Window)
Return win
End If
Now
This works well, but when I use win.Close to close my window I get an error when trying to open it again with win.Show, although I create an new instance of Window?
System.InvalidOperationException
Cannot set Visibility or call Show, ShowDialog... after a Window has
closed.
But it works when I don't use the Dictionary Method but the XAMLReader.Load directly - any ideas whats going on ? Somehow the window I get by returning XamlReader.Load seems different than the stored information from the dict?? Am I missing somehting? Thanks in advance!
You could use Hide() instead of Close()
Hide hides the Form, so instead of disposing of the form (and its controls) you make it invisible. Show will make it visible again.
Be careful though, the form in the dictionary will still hold the state from the previous time it was used.

Handling events from user control containing a WPF textbox

In order to take advantage of the spell checking ability of WPF textboxes, I have added one to a user control (with the use of elementhost). This user control is used in various window forms. My current problem is trying to handle keyup events from this textbox but the windows form is unable to "get" any event from the control. I can access the properties of the textbox just fine (i.e. text, length, etc.) but keyboard events don't seem to work.
I have found, however, that the following will bring back events from the WPF textbox:
Public Class MyUserControl
Private _elementHost As New ElementHost
Private _wpfTextbox As New System.Windows.Controls.Textbox
Private Sub MyUserControl_Load(...) Handles Me.Load
Me.Controls.Add(_elementHost)
_elementHost.Dock = DockStyle.Fill
_elementHost.Child = _wpfTextbox
Dim MyEventInfo As EventInfo
Dim MyMethodInfo As MethodInfo
MyMethodInfo = Me.GetType().GetMethod("WPFTextbox_KeyUp")
MyEventInfo = _wpfTextBox.GetType().GetEvent("PreviewKeyUp")
Dim dlg As [Delegate] = [Delegate].CreateDelegate(MyEventInfo.EventHandlerType, Me, MyMethodInfo)
MyEventInfo.AddEventHandler(_wpfTextBox, dlg)
End Sub
Public Sub WPFTextbox_KeyUp(ByVal sender As Object, ByVal e As RoutedEventArgs)
' something goes here
End Sub
End Class
The user control is now able to do something after the PreviewKeyUp event is fired in the WPF textbox. Now, I'm not completely sure how to have the window form containing this user control to work with this.
Im a C# person not VB so please bear with me.. Basically you could assign the event from your Window rather than within your UserControl.. So in the constructor of your Window assign the PreviewKeyUp:
this.myUserContorl.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
then place the event handler in your Window:
private void WPFTextbox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
}
Incidentally you needn't go through the hassle of capturing the event in your UserControl as you can still access your TextBox within you UserControl directly from your Window (if you make it public), again from your constructor in your Window:
this.myUserContorl.wpfTextbox.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
I imagine it would look like this in VB (at a guess):
AddHandler myUserContorl.wpfTextbox.PreviewKeyUp, AddressOf WPFTextbox_KeyUp
ElementHost has a static method called EnableModelessKeyboardInterop(). Try calling it?
ElementHost.EnableModelessKeyboardInterop();
Read more here
Seems basic, but did you set the KeyPreview to TRUE on your form?

.NET ListView and Windows 7

Maybe I'm missing something, but...
The ListView control in Windows 7 displays a highlight around selected items that looks like a 3D blue translucent rectangle (I'm not talking about the selection rectangle, but the rectangle around the actual selected items). It even shows a lighter rectangle when hovering over items.
However, when I use the ListView in WinForms (even when double-buffered), the selected items just have a plain blue background (and no hover background) which looks much less professional than, say, the list in Explorer.
Does anyone know what secret API function I should call to make the .NET ListView look in line with the rest of the OS?
For example, here is one of my applications written in C++, using a standard ListView control in Windows 7: (notice the highlight and hover rectangle)
And here is a rewrite of that application in C# with WinForms: (notice the crude highlight and no hover)
OK, I totally figured it out, and this may help others who are bothered by this issue.
I began by noticing that the ListView control in C++Builder looks "correct" under Windows 7, so I looked in the source code for the VCL to see what kind of magic they're doing to make the ListView look like the list control in Windows Explorer. I stumbled on one line of code that looked promising:
SetWindowTheme(Handle, 'explorer', nil);
From the SDK documentation, this function "Causes a window to use a different set of visual style information than its class normally uses."
So, I tried invoking this function on my WinForms ListView control:
[DllImport("uxtheme.dll", CharSet = CharSet.Unicode)]
public static extern int SetWindowTheme(IntPtr hWnd, String pszSubAppName, String pszSubIdList);
SetWindowTheme(myListView.Handle, "explorer", null);
...and, by god, it worked! The ListView finally looks like it belongs with the rest of the OS! Thanks, Borland Inprise Embarcadero! You really are good for something!
Imports System.Runtime.InteropServices
Public Class Form1
<DllImport("uxtheme", CharSet:=CharSet.Unicode)> _
Public Shared Function SetWindowTheme(ByVal hWnd As IntPtr, ByVal textSubAppName As String, ByVal textSubIdList As String) As Integer
End Function
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
SetWindowTheme(lst.Handle, "explorer", Nothing)
End Sub
End Class
The above code will work like a champ...
edit: now it is working for me too, the exact signature is:
<DllImport("uxtheme.dll",
BestFitMapping:=False,
CharSet:=CharSet.Unicode,
EntryPoint:="#136",
CallingConvention:=CallingConvention.Winapi)>
Private Shared Function SetWindowsTheme(ByVal handle As IntPtr, ByVal app As String, ByVal id As String) As Integer
' Leave function empty - DLLImport attribute forwards calls to the right function
End Function
Public Shared Sub MakeControlLookBeautiful(ByVal c As Windows.Forms.Control)
SetWindowsTheme(c.Handle, "explorer", Nothing)
End Sub
:)

Resources