|
//------------------------------------------------------------------------------
// <copyright file="Control.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
/*
*/
namespace System.Windows.Forms {
using Accessibility;
using Microsoft.Win32;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
using System.Configuration.Assemblies;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.Security.Permissions;
using System.Security;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Remoting;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;
using System.Windows.Forms.Design;
using System.Windows.Forms.Internal;
using Encoding = System.Text.Encoding;
using System.Drawing.Imaging;
using System.Windows.Forms.Layout;
/// <devdoc>
/// Control's IME feature.
/// </devdoc>
public partial class Control :
Component,
UnsafeNativeMethods.IOleControl,
UnsafeNativeMethods.IOleObject,
UnsafeNativeMethods.IOleInPlaceObject,
UnsafeNativeMethods.IOleInPlaceActiveObject,
UnsafeNativeMethods.IOleWindow,
UnsafeNativeMethods.IViewObject,
UnsafeNativeMethods.IViewObject2,
UnsafeNativeMethods.IPersist,
UnsafeNativeMethods.IPersistStreamInit,
UnsafeNativeMethods.IPersistPropertyBag,
UnsafeNativeMethods.IPersistStorage,
UnsafeNativeMethods.IQuickActivate,
ISupportOleDropSource,
IDropTarget,
ISynchronizeInvoke,
IWin32Window,
IArrangedElement,
IBindableComponent {
// See IME feature spec at http://dotnetclient/Whidbey/dev/General%20Documentation/Microsoft.Ime.doc
/// <devdoc>
/// Constants starting/ending the WM_CHAR messages to ignore count. See ImeWmCharsToIgnore property.
/// </devdoc>
private const int ImeCharsToIgnoreDisabled = -1;
private const int ImeCharsToIgnoreEnabled = 0;
/// <devdoc>
/// The ImeMode value for controls with ImeMode = ImeMode.NoControl. See PropagatingImeMode property.
/// </devdoc>
private static ImeMode propagatingImeMode = ImeMode.Inherit; // Inherit means uninitialized.
/// <devdoc>
/// This flag prevents resetting ImeMode value of the focused control. See IgnoreWmImeNotify property.
/// </devdoc>
private static bool ignoreWmImeNotify;
/// <devdoc>
/// This flag works around an Issue with the Chinese IME sending IMENotify messages prior to WmInputLangChange
/// which would cause this code to use OnHalf as the default mode overriding .ImeMode property. See WmImeNotify
/// </devdoc>
private static bool lastLanguageChinese = false;
/// <devdoc>
/// The ImeMode in the property store.
/// </devdoc>
internal ImeMode CachedImeMode {
get {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_CachedImeMode(), this = " + this );
Debug.Indent();
// Get the ImeMode from the property store
//
bool found;
ImeMode cachedImeMode = (ImeMode) Properties.GetInteger( PropImeMode, out found );
if( !found ) {
cachedImeMode = DefaultImeMode;
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "using DefaultImeMode == " + cachedImeMode );
}
else {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "using property store ImeMode == " + cachedImeMode );
}
// If inherited, get the mode from this control's parent
//
if( cachedImeMode == ImeMode.Inherit ) {
Control parent = ParentInternal;
if( parent != null ) {
cachedImeMode = parent.CachedImeMode;
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "inherited from parent = " + parent.GetType() );
}
else {
cachedImeMode = ImeMode.NoControl;
}
}
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "returning CachedImeMode == " + cachedImeMode );
Debug.Unindent();
return cachedImeMode;
}
set {
// WARNING: When the control is in restricted mode (!CanEnableIme) the CachedImeMode should be changed only programatically,
// calls generated by user interaction should be wrapped with a check for CanEnableIme.
//
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside set_CachedImeMode(), this = " + this );
Debug.Indent();
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Warning, "Setting cached Ime == " + value );
Properties.SetInteger( PropImeMode, (int) value );
Debug.Unindent();
}
}
/// <devdoc>
/// Specifies whether the ImeMode property value can be changed to an active value.
/// Added to support Password & ReadOnly (and maybe other) properties, which when set, should force disabling
/// the IME if using one.
/// </devdoc>
protected virtual bool CanEnableIme {
get {
// Note: If overriding this property make sure to add the Debug tracing code and call this method (base.CanEnableIme).
Debug.Indent();
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format(CultureInfo.CurrentCulture, "Inside get_CanEnableIme(), value = {0}, this = {1}", ImeSupported, this ) );
Debug.Unindent();
return ImeSupported;
}
}
/// <devdoc>
/// Gets the current IME context mode. If no IME associated, ImeMode.Inherit is returned.
/// </devdoc>
internal ImeMode CurrentImeContextMode {
get {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_CurrentImeContextMode(), this = " + this );
if( this.IsHandleCreated ) {
return ImeContext.GetImeMode( this.Handle );
}
else {
// window is not yet created hence no IME associated yet.
return ImeMode.Inherit;
}
}
}
/// <devdoc>
/// </devdoc>
protected virtual ImeMode DefaultImeMode {
get { return ImeMode.Inherit; }
}
/// <devdoc>
/// Flag used to avoid re-entrancy during WM_IME_NOTFIY message processing - see WmImeNotify().
/// Also to avoid raising the ImeModeChanged event more than once during the process of changing the ImeMode.
/// </devdoc>
internal int DisableImeModeChangedCount {
get {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_DisableImeModeChangedCount()" );
Debug.Indent();
bool dummy;
int val = (int) Properties.GetInteger( PropDisableImeModeChangedCount, out dummy );
Debug.Assert( val >= 0, "Counter underflow." );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Value: " + val );
Debug.Unindent();
return val;
}
set {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside set_DisableImeModeChangedCount(): " + value );
Properties.SetInteger(PropDisableImeModeChangedCount, value);
}
}
/// <devdoc>
/// Flag used to prevent setting ImeMode in focused control when losing focus and hosted in a non-Form shell.
/// See WmImeKillFocus() for more info.
/// </devdoc>
private static bool IgnoreWmImeNotify {
get {
Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_IgnoreWmImeNotify()");
Debug.Indent();
bool val = Control.ignoreWmImeNotify;
Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Value: " + val);
Debug.Unindent();
return val;
}
set {
Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside set_IgnoreWmImeNotify(): " + value);
Control.ignoreWmImeNotify = value;
}
}
/// <include file='doc\Control.uex' path='docs/doc[@for="Control.ImeMode"]/*' />
/// <devdoc>
/// Specifies a value that determines the IME (Input Method Editor) status of the
/// object when that object is selected.
/// </devdoc>
[
SRCategory( SR.CatBehavior ),
Localizable( true ),
AmbientValue( ImeMode.Inherit ),
SRDescription( SR.ControlIMEModeDescr )
]
public ImeMode ImeMode {
get {
ImeMode imeMode = ImeModeBase;
if (imeMode == ImeMode.OnHalf) // This is for compatibility. See QFE#4448.
{
imeMode = ImeMode.On;
}
return imeMode;
}
set {
ImeModeBase = value;
}
}
/// <devdoc>
/// Internal version of ImeMode property. This is provided for controls that override CanEnableIme and that
/// return ImeMode.Disable for the ImeMode property when CanEnableIme is false - See TextBoxBase controls.
/// </devdoc>
protected virtual ImeMode ImeModeBase {
get {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_ImeModeBase(), this = " + this );
Debug.Indent();
ImeMode imeMode = CachedImeMode;
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Value = " + imeMode );
Debug.Unindent();
return imeMode;
}
set {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format( CultureInfo.CurrentCulture, "Inside set_ImeModeBase({0}), this = {1}", value, this ) );
Debug.Indent();
//valid values are -1 to 0xb
if( !ClientUtils.IsEnumValid( value, (int) value, (int) ImeMode.Inherit, (int) ImeMode.OnHalf ) ) {
throw new InvalidEnumArgumentException( "ImeMode", (int) value, typeof( ImeMode ) );
}
ImeMode oldImeMode = CachedImeMode;
CachedImeMode = value;
if( oldImeMode != value ) {
// Cache current value to determine whether we need to raise the ImeModeChanged.
Control ctl = null;
if( !DesignMode && ImeModeConversion.InputLanguageTable != ImeModeConversion.UnsupportedTable ) {
// Set the context to the new value if control is focused.
if( Focused ) {
ctl = this;
}
else if( ContainsFocus ) {
ctl = FromChildHandleInternal( UnsafeNativeMethods.GetFocus() );
}
if( ctl != null && ctl.CanEnableIme ) {
// Block ImeModeChanged since we are checking for it below.
DisableImeModeChangedCount++;
try {
ctl.UpdateImeContextMode();
}
finally {
DisableImeModeChangedCount--;
}
}
}
VerifyImeModeChanged( oldImeMode, CachedImeMode );
}
ImeContext.TraceImeStatus( this );
Debug.Unindent();
}
}
/// <devdoc>
/// Determines whether the Control supports IME handling by default.
/// </devdoc>
private bool ImeSupported {
get {
return DefaultImeMode != ImeMode.Disable;
}
}
/// <include file='doc\Control.uex' path='docs/doc[@for="Control.ImeModeChanged"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
[WinCategory( "Behavior" ), SRDescription( SR.ControlOnImeModeChangedDescr )]
public event EventHandler ImeModeChanged {
add {
Events.AddHandler( EventImeModeChanged, value );
}
remove {
Events.RemoveHandler( EventImeModeChanged, value );
}
}
/// <devdoc>
/// Returns the current number of WM_CHAR messages to ignore after processing corresponding WM_IME_CHAR msgs.
/// </devdoc>
internal int ImeWmCharsToIgnore {
// The IME sends WM_IME_CHAR messages for each character in the composition string, and then
// after all messages are sent, corresponding WM_CHAR messages are also sent. (in non-unicode
// windows two WM_CHAR messages are sent per char in the IME). We need to keep a counter
// not to process each character twice or more.
// Marshal.SystemDefaultCharSize represents the default character size on the system; the default
// is 2 for Unicode systems and 1 for ANSI systems. This is how it is implemented in Control.
get {
return Properties.GetInteger( PropImeWmCharsToIgnore );
}
set {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format( CultureInfo.CurrentCulture, "Inside set_ImeWmCharToIgnore(value={0}), this = {1}", value, this ) );
Debug.Indent();
// WM_CHAR is not send after WM_IME_CHAR when the composition has been closed by either, changing the conversion mode or
// dissasociating the IME (for instance when loosing focus and conversion is forced to complete).
if( ImeWmCharsToIgnore != ImeCharsToIgnoreDisabled ) {
Properties.SetInteger( PropImeWmCharsToIgnore, value );
}
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImeWmCharsToIgnore on leaving setter: " + ImeWmCharsToIgnore );
Debug.Unindent();
}
}
/// <devdoc>
/// Gets the last value CanEnableIme property when it was last checked for ensuring IME context restriction mode.
/// This is used by controls that implement some sort of IME restriction mode (like TextBox on Password/ReadOnly mode).
/// See the VerifyImeRestrictedModeChanged() method.
/// </devdoc>
private bool LastCanEnableIme {
get {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_LastCanEnableIme()" );
Debug.Indent();
bool valueFound;
int val = (int) Properties.GetInteger( PropLastCanEnableIme, out valueFound );
if( valueFound ) {
valueFound = val == 1;
}
else {
valueFound = true;
}
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Value: " + valueFound );
Debug.Unindent();
return valueFound;
}
set {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside set_LastCanEnableIme(): " + value );
Properties.SetInteger( PropLastCanEnableIme, value ? 1 : 0 );
}
}
/// <devdoc>
/// Represents the internal ImeMode value for controls with ImeMode = ImeMode.NoControl. This property is changed
/// only by user interaction and is required to set the IME context appropriately while keeping the ImeMode property
/// unchanged.
/// </devdoc>
protected static ImeMode PropagatingImeMode {
get {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_PropagatingImeMode()" );
Debug.Indent();
if( Control.propagatingImeMode == ImeMode.Inherit ) {
// Initialize the propagating IME mode to the value the IME associated to the focused window currently has,
// this enables propagating the IME mode from/to unmanaged applications hosting Microsoft controls.
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "Initializing PropagatingImeMode" );
ImeMode imeMode = ImeMode.Inherit;
IntPtr focusHandle = UnsafeNativeMethods.GetFocus();
if( focusHandle != IntPtr.Zero ) {
imeMode = ImeContext.GetImeMode(focusHandle);
// If focused control is disabled we won't be able to get the app ime context mode, try the top window.
// this is the case of a disabled Microsoft control hosted in a non-Form shell.
if( imeMode == ImeMode.Disable ) {
focusHandle = UnsafeNativeMethods.GetAncestor(new HandleRef(null, focusHandle), NativeMethods.GA_ROOT);
if( focusHandle != IntPtr.Zero ) {
imeMode = ImeContext.GetImeMode(focusHandle);
}
}
}
// If IME is disabled the PropagatingImeMode will not be initialized, see property setter below.
PropagatingImeMode = imeMode;
}
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "Value: " + Control.propagatingImeMode );
Debug.Unindent();
return Control.propagatingImeMode;
}
private set {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside set_PropagatingImeMode()" );
Debug.Indent();
if (Control.propagatingImeMode != value) {
switch( value ) {
case ImeMode.NoControl:
case ImeMode.Disable:
// Cannot set propagating ImeMode to one of these values.
Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "Cannot change PropagatingImeMode to " + value);
return;
default:
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Warning, string.Format( CultureInfo.CurrentCulture, "Setting PropagatingImeMode: Current value = {0}, New value = {1}", propagatingImeMode, value ) );
Control.propagatingImeMode = value;
break;
}
}
Debug.Unindent();
}
}
/// <devdoc>
/// Sets the IME context to the appropriate ImeMode according to the control's ImeMode state.
/// This method is commonly used when attaching the IME to the control's window.
/// </devdoc>
internal void UpdateImeContextMode() {
ImeMode[] inputLanguageTable = ImeModeConversion.InputLanguageTable;
if (!DesignMode && (inputLanguageTable != ImeModeConversion.UnsupportedTable) && Focused) {
// Note: CHN IME won't send WM_IME_NOTIFY msg when getting associated, setting the IME context mode
// forces the message to be sent as a side effect.
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside UpdateImeContextMode(), this = " + this );
Debug.Indent();
// If the value is not supported by the Ime, it will be mapped to a corresponding one, we need
// to update the cached ImeMode to the actual value.
ImeMode newImeContextMode = ImeMode.Disable;
ImeMode currentImeMode = CachedImeMode;
if( ImeSupported && CanEnableIme ) {
newImeContextMode = currentImeMode == ImeMode.NoControl ? PropagatingImeMode : currentImeMode;
}
// If PropagatingImeMode has not been initialized it will return ImeMode.Inherit above, need to check newImeContextMode for this.
if (CurrentImeContextMode != newImeContextMode && newImeContextMode != ImeMode.Inherit) {
// If the context changes the window will receive one or more WM_IME_NOTIFY messages and as part of its
// processing it will raise the ImeModeChanged event if needed. We need to prevent the event from been
// raised here from here.
DisableImeModeChangedCount++;
// Setting IME status to Disable will first close the IME and then disable it. For CHN IME, the first action will
// update the PropagatingImeMode to ImeMode.Close which is incorrect. We need to save the PropagatingImeMode in
// this case and restore it after the context has been changed. See VSW#530471
// Also this call here is very important since it will initialize the PropagatingImeMode if not already initialized
// before setting the IME context to the control's ImeMode value which could be different from the propagating value.
ImeMode savedPropagatingImeMode = PropagatingImeMode;
try {
ImeContext.SetImeStatus( newImeContextMode, this.Handle );
}
finally {
DisableImeModeChangedCount--;
if (newImeContextMode == ImeMode.Disable && inputLanguageTable == ImeModeConversion.ChineseTable) {
// Restore saved propagating mode.
PropagatingImeMode = savedPropagatingImeMode;
}
}
// Get mapped value from the context.
if( currentImeMode == ImeMode.NoControl ) {
if( CanEnableIme ) {
PropagatingImeMode = CurrentImeContextMode;
}
}
else {
if( CanEnableIme ) {
CachedImeMode = CurrentImeContextMode;
}
// Need to raise the ImeModeChanged event?
VerifyImeModeChanged( newImeContextMode, CachedImeMode );
}
}
ImeContext.TraceImeStatus( this );
Debug.Unindent();
}
}
/// <devdoc>
/// Checks if specified ImeMode values are different and raise the event if true.
/// </devdoc>
private void VerifyImeModeChanged( ImeMode oldMode, ImeMode newMode ) {
if( ImeSupported && (DisableImeModeChangedCount == 0) && (newMode != ImeMode.NoControl) ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format( CultureInfo.CurrentCulture, "Inside VerifyImeModeChanged(oldMode={0}, newMode={1}), this = {2}", oldMode, newMode, this ) );
Debug.Indent();
if( oldMode != newMode ) {
OnImeModeChanged( EventArgs.Empty );
}
Debug.Unindent();
}
}
/// <devdoc>
/// Verifies whether the IME context mode is correct based on the control's Ime restriction mode (CanEnableIme)
/// and updates the IME context if needed.
/// </devdoc>
internal void VerifyImeRestrictedModeChanged() {
Debug.Assert( ImeSupported, "This method should not be called from controls that don't support IME input." );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside VerifyImeRestrictedModeChanged(), this = " + this );
Debug.Indent();
bool currentCanEnableIme = this.CanEnableIme;
if( LastCanEnableIme != currentCanEnableIme ) {
if( Focused ) {
// Disable ImeModeChanged from the following call since we'll raise it here if needed.
DisableImeModeChangedCount++;
try {
UpdateImeContextMode();
}
finally {
DisableImeModeChangedCount--;
}
}
// Assume for a moment the control is getting restricted;
ImeMode oldImeMode = CachedImeMode;
ImeMode newImeMode = ImeMode.Disable;
if( currentCanEnableIme ) {
// Control is actually getting unrestricted, swap values.
newImeMode = oldImeMode;
oldImeMode = ImeMode.Disable;
}
// Do we need to raise the ImeModeChanged event?
VerifyImeModeChanged( oldImeMode, newImeMode );
// Finally update the saved CanEnableIme value.
LastCanEnableIme = currentCanEnableIme;
}
Debug.Unindent();
}
/// <devdoc>
/// Update internal ImeMode properties (PropagatingImeMode/CachedImeMode) with actual IME context mode if needed.
/// This method can be used with a child control when the IME mode is more relevant to it than to the control itself,
/// for instance ComboBox and its native ListBox/Edit controls.
/// </devdoc>
internal void OnImeContextStatusChanged( IntPtr handle ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside OnImeContextStatusChanged(), this = " + this );
Debug.Indent();
Debug.Assert( ImeSupported, "WARNING: Attempting to update ImeMode properties on IME-Unaware control!" );
Debug.Assert( !DesignMode, "Shouldn't be updating cached ime mode at design-time" );
ImeMode fromContext = ImeContext.GetImeMode( handle );
if( fromContext != ImeMode.Inherit ) {
ImeMode oldImeMode = CachedImeMode;
if( CanEnableIme ) { // Cache or Propagating ImeMode should not be updated by interaction when the control is in restricted mode.
if( oldImeMode != ImeMode.NoControl ) {
CachedImeMode = fromContext; // This could end up in the same value due to ImeMode language mapping.
// ImeMode may be changing by user interaction.
VerifyImeModeChanged( oldImeMode, CachedImeMode );
}
else {
PropagatingImeMode = fromContext;
}
}
}
Debug.Unindent();
}
/// <include file='doc\Control.uex' path='docs/doc[@for="Control.OnImeModeChanged"]/*' />
/// <devdoc>
/// <para>Raises the <see cref='System.Windows.Forms.Control.OnImeModeChanged'/>
/// event.</para>
/// </devdoc>
protected virtual void OnImeModeChanged( EventArgs e ) {
Debug.Assert( ImeSupported, "ImeModeChanged should not be raised on an Ime-Unaware control." );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside OnImeModeChanged(), this = " + this );
EventHandler handler = (EventHandler) Events[EventImeModeChanged];
if( handler != null ) handler( this, e );
}
/// <devdoc>
/// Resets the Ime mode.
/// </devdoc>
[EditorBrowsable( EditorBrowsableState.Never )]
public void ResetImeMode() {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside ResetImeMode(), this = " + this );
ImeMode = DefaultImeMode;
}
/// <devdoc>
/// Returns true if the ImeMode should be persisted in code gen.
/// </devdoc>
[EditorBrowsable( EditorBrowsableState.Never )]
internal virtual bool ShouldSerializeImeMode() {
// This method is for designer support. If the ImeMode has not been changed or it is the same as the
// default value it should not be serialized.
bool found;
int imeMode = Properties.GetInteger( PropImeMode, out found );
return ( found && imeMode != (int) DefaultImeMode );
}
/// <devdoc>
/// Handles the WM_INPUTLANGCHANGE message
/// </devdoc>
/// <internalonly/>
private void WmInputLangChange( ref Message m ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmInputLangChange(), this = " + this );
Debug.Indent();
// Make sure the IME context is associated with the correct (mapped) mode.
UpdateImeContextMode();
// If detaching IME (setting to English) reset propagating IME mode so when reattaching the IME is set to direct input again.
if( ImeModeConversion.InputLanguageTable == ImeModeConversion.UnsupportedTable ) {
PropagatingImeMode = ImeMode.Off;
}
if( LocalAppContextSwitches.EnableLegacyChineseIMEIndicator && ImeModeConversion.InputLanguageTable == ImeModeConversion.ChineseTable ) {
IgnoreWmImeNotify = false;
}
Form form = FindFormInternal();
if( form != null ) {
InputLanguageChangedEventArgs e = InputLanguage.CreateInputLanguageChangedEventArgs( m );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Culture=" + e.Culture );
form.PerformOnInputLanguageChanged( e );
}
DefWndProc( ref m );
ImeContext.TraceImeStatus( this );
Debug.Unindent();
}
/// <devdoc>
/// Handles the WM_INPUTLANGCHANGEREQUEST message
/// </devdoc>
/// <internalonly/>
private void WmInputLangChangeRequest( ref Message m ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmInputLangChangeRequest(), this=" + this );
Debug.Indent();
InputLanguageChangingEventArgs e = InputLanguage.CreateInputLanguageChangingEventArgs( m );
Form form = FindFormInternal();
if( form != null ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Culture=" + e.Culture );
form.PerformOnInputLanguageChanging( e );
}
if( !e.Cancel ) {
DefWndProc( ref m );
}
else {
m.Result = IntPtr.Zero;
}
Debug.Unindent();
}
/// <devdoc>
/// Handles the WM_IME_CHAR message
/// </devdoc>
private void WmImeChar( ref Message m ) {
if( ProcessKeyEventArgs( ref m ) ) {
return;
}
DefWndProc( ref m );
}
/// <devdoc>
/// Handles the WM_IME_ENDCOMPOSITION message
/// </devdoc>
private void WmImeEndComposition( ref Message m ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmImeEndComposition() - Disabling ImeWmCharToIgnore, this=" + this );
this.ImeWmCharsToIgnore = ImeCharsToIgnoreDisabled;
DefWndProc( ref m );
}
/// <devdoc>
/// Handles the WM_IME_NOTIFY message
/// </devdoc>
private void WmImeNotify( ref Message m ) {
ImeMode[] inputLanguageTable = ImeModeConversion.InputLanguageTable;
// During a change to the Chinese language with Focus already set, the Chinese IME will send several WmImeNotify messages
// before ever sending a WmInputLangChange event. Also, the IME will report an IME input context during this time that we
// interpret as On = 'OnHalf'. The combination of these causes us to update the default Cached ImeMode to OnHalf, overriding
// the control's ImeMode property -- unwanted behavior. We workaround this by skipping our mode synchronization during these
// IMENotify messages until we get a WmInputLangChange event.
//
// If this is the first time here after conversion to chinese language, wait for WmInputLanguageChange
// before listening to WmImeNotifys.
if ( LocalAppContextSwitches.EnableLegacyChineseIMEIndicator && (inputLanguageTable == ImeModeConversion.ChineseTable) && !lastLanguageChinese) {
IgnoreWmImeNotify = true;
}
lastLanguageChinese = (inputLanguageTable == ImeModeConversion.ChineseTable);
if( ImeSupported && inputLanguageTable != ImeModeConversion.UnsupportedTable && !IgnoreWmImeNotify) {
int wparam = (int) m.WParam;
// The WM_IME_NOTIFY message is not consistent across the different IMEs, particularly the notification type
// we care about (IMN_SETCONVERSIONMODE & IMN_SETOPENSTATUS).
// The IMN_SETOPENSTATUS command is sent when the open status of the input context is updated.
// The IMN_SETCONVERSIONMODE command is sent when the conversion mode of the input context is updated.
// - The Korean IME sents both msg notifications when changing the conversion mode (From/To Hangul/Alpha).
// - The Chinese IMEs sends the IMN_SETCONVERSIONMODE when changing mode (On/Close, Full Shape/Half Shape)
// and IMN_SETOPENSTATUS when getting disabled/enabled or closing/opening as well, but it does not send any
// WM_IME_NOTIFY when associating an IME to the app for the first time; setting the IME mode to direct input
// during WM_INPUTLANGCHANGED forces the IMN_SETOPENSTATUS message to be sent.
// - The Japanese IME sends IMN_SETCONVERSIONMODE when changing from Off to one of the active modes (Katakana..)
// and IMN_SETOPENSTATUS when changing beteween the active modes or when enabling/disabling the IME.
// In any case we update the cache.
// Warning:
// Attempting to change the IME mode from here will cause re-entrancy - WM_IME_NOTIFY is resent.
// We guard against re-entrancy since the ImeModeChanged event can be raised and any changes from the handler could
// lead to another WM_IME_NOTIFY loop.
if( wparam == NativeMethods.IMN_SETCONVERSIONMODE || wparam == NativeMethods.IMN_SETOPENSTATUS ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format( CultureInfo.CurrentCulture, "Inside WmImeNotify(m.wparam=[{0}]), this={1}", m.WParam, this ) );
Debug.Indent();
// Synchronize internal properties with the IME context mode.
OnImeContextStatusChanged( this.Handle );
Debug.Unindent();
}
}
DefWndProc( ref m );
}
/// <devdoc>
/// Handles the WM_SETFOCUS message for IME related stuff.
/// </devdoc>
internal void WmImeSetFocus() {
if (ImeModeConversion.InputLanguageTable != ImeModeConversion.UnsupportedTable) {
Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmImeSetFocus(), this=" + this);
Debug.Indent();
// Make sure the IME context is set to the correct value.
// Consider - Perf improvement: ContainerControl controls should update the IME context only when they don't contain
// a focusable control since it will be updated by that control.
UpdateImeContextMode();
Debug.Unindent();
}
}
/// <devdoc>
/// Handles the WM_IME_STARTCOMPOSITION message
/// </devdoc>
private void WmImeStartComposition( ref Message m ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmImeStartComposition() - Enabling ImeWmCharToIgnore, this=" + this );
// Need to call the property store directly because the WmImeCharsToIgnore property is locked when ImeCharsToIgnoreDisabled.
Properties.SetInteger( PropImeWmCharsToIgnore, ImeCharsToIgnoreEnabled );
DefWndProc( ref m );
}
/// <devdoc>
/// Handles the WM_KILLFOCUS message
/// </devdoc>
/// <internalonly/>
private void WmImeKillFocus() {
Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside WmImeKillFocus(), this=" + this);
Debug.Indent();
Control topMostWinformsParent = TopMostParent;
Form appForm = topMostWinformsParent as Form;
if( (appForm == null || appForm.Modal) && !topMostWinformsParent.ContainsFocus ) {
// This means the Microsoft component container is not a Microsoft host and it is no longer focused.
// Or it is not the main app host. See VSW#531520,604741 (VSU QFE#5055), DDB#95889, TFS VSTSDEVDIV463760
Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Unfocused TopMostParent = " + topMostWinformsParent);
// We need to reset the PropagatingImeMode to force reinitialization when the Microsoft component gets focused again;
// this enables inheritting the propagating mode from an unmanaged application hosting a Microsoft component.
// But before leaving the Microsoft container we need to set the IME to the propagating IME mode since the focused control
// may not support IME which would leave the IME disabled.
// See the PropagatingImeMode property and VSW#531520.
// Note: We need to check the static field here directly to avoid initialization of the property.
if (Control.propagatingImeMode != ImeMode.Inherit) {
// Setting the ime context of the top window will generate a WM_IME_NOTIFY on the focused control which will
// update its ImeMode, we need to prevent this temporarily.
IgnoreWmImeNotify = true;
try {
Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "Setting IME context to PropagatingImeMode (leaving Microsoft container). this = " + this);
ImeContext.SetImeStatus(PropagatingImeMode, topMostWinformsParent.Handle);
Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "Resetting PropagatingImeMode. this = " + this);
PropagatingImeMode = ImeMode.Inherit;
}
finally {
IgnoreWmImeNotify = false;
}
}
}
Debug.Unindent();
}
} // end class Control
///////////////////////////////////////////////////////// ImeContext class /////////////////////////////////////////////////////////
/// <devdoc>
/// Represents the native IME context.
/// </devdoc>
public static class ImeContext {
/// <devdoc>
/// The IME context handle obtained when first associating an IME.
/// </devdoc>
private static IntPtr originalImeContext;
/// <devdoc>
/// Disable the IME
/// </devdoc>
public static void Disable( IntPtr handle ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside ImeContext.Disable(" + handle + ")" );
Debug.Indent();
if( ImeModeConversion.InputLanguageTable != ImeModeConversion.UnsupportedTable ) {
// Close the IME if necessary
//
if( ImeContext.IsOpen( handle ) ) {
ImeContext.SetOpenStatus( false, handle );
}
// Disable the IME by disassociating the context from the window.
//
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmAssociateContext(" + handle + ", null)" );
IntPtr oldContext = UnsafeNativeMethods.ImmAssociateContext( new HandleRef( null, handle ), NativeMethods.NullHandleRef );
if( oldContext != IntPtr.Zero ) {
originalImeContext = oldContext;
}
}
ImeContext.TraceImeStatus( handle );
Debug.Unindent();
}
/// <devdoc>
/// Enable the IME
/// </devdoc>
public static void Enable( IntPtr handle ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside ImeContext.Enable(" + handle + ")" );
Debug.Indent();
if( ImeModeConversion.InputLanguageTable != ImeModeConversion.UnsupportedTable ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetContext(" + handle + ")" );
IntPtr inputContext = UnsafeNativeMethods.ImmGetContext( new HandleRef( null, handle ) );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "context = " + inputContext );
// Enable IME by associating the IME context to the window.
if( inputContext == IntPtr.Zero ) {
if( originalImeContext == IntPtr.Zero ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmCreateContext()" );
inputContext = UnsafeNativeMethods.ImmCreateContext();
if( inputContext != IntPtr.Zero ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmAssociateContext(" + handle + ", " + inputContext + ")" );
UnsafeNativeMethods.ImmAssociateContext( new HandleRef( null, handle ), new HandleRef( null, inputContext ) );
}
}
else {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmAssociateContext()" );
UnsafeNativeMethods.ImmAssociateContext( new HandleRef( null, handle ), new HandleRef( null, originalImeContext ) );
}
}
else {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmReleaseContext(" + handle + ", " + inputContext + ")" );
UnsafeNativeMethods.ImmReleaseContext( new HandleRef( null, handle ), new HandleRef( null, inputContext ) );
}
// Make sure the IME is opened.
if( !ImeContext.IsOpen( handle ) ) {
ImeContext.SetOpenStatus( true, handle );
}
}
ImeContext.TraceImeStatus( handle );
Debug.Unindent();
}
/// <devdoc>
/// Gets the ImeMode that corresponds to ImeMode.Disable based on the current input language ImeMode table.
/// </devdoc>
public static ImeMode GetImeMode( IntPtr handle ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Insise ImeContext.GetImeMode(" + handle + ")" );
Debug.Indent();
IntPtr inputContext = IntPtr.Zero;
ImeMode retval = ImeMode.NoControl;
// Get the right table for the current keyboard layout
//
ImeMode[] countryTable = ImeModeConversion.InputLanguageTable;
if( countryTable == ImeModeConversion.UnsupportedTable ) {
// No IME associated with current culture.
retval = ImeMode.Inherit;
goto cleanup;
}
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetContext(" + handle + ")" );
inputContext = UnsafeNativeMethods.ImmGetContext( new HandleRef( null, handle ) );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "context = " + inputContext );
if( inputContext == IntPtr.Zero ) {
// No IME context attached - The Ime has been disabled.
retval = ImeMode.Disable;
goto cleanup;
}
if( !ImeContext.IsOpen( handle ) ) {
// There's an IME associated with the window but is closed - the input is taken from the keyboard as is (English).
retval = countryTable[ImeModeConversion.ImeClosed];
goto cleanup;
}
// Determine the IME mode from the conversion status
//
int conversion = 0; // Combination of conversion mode values
int sentence = 0; // Sentence mode value
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetConversionStatus(" + inputContext + ", conversion, sentence)" );
UnsafeNativeMethods.ImmGetConversionStatus( new HandleRef( null, inputContext ), ref conversion, ref sentence );
Debug.Assert( countryTable != null, "countryTable is null" );
if( ( conversion & NativeMethods.IME_CMODE_NATIVE ) != 0 ) {
if( ( conversion & NativeMethods.IME_CMODE_KATAKANA ) != 0 ) {
retval = ( ( conversion & NativeMethods.IME_CMODE_FULLSHAPE ) != 0 )
? countryTable[ImeModeConversion.ImeNativeFullKatakana]
: countryTable[ImeModeConversion.ImeNativeHalfKatakana];
goto cleanup;
}
else { // ! Katakana
retval = ( ( conversion & NativeMethods.IME_CMODE_FULLSHAPE ) != 0 )
? countryTable[ImeModeConversion.ImeNativeFullHiragana]
: countryTable[ImeModeConversion.ImeNativeHalfHiragana];
goto cleanup;
}
}
else { // ! IME_CMODE_NATIVE
retval = ( ( conversion & NativeMethods.IME_CMODE_FULLSHAPE ) != 0 )
? countryTable[ImeModeConversion.ImeAlphaFull]
: countryTable[ImeModeConversion.ImeAlphaHalf];
}
cleanup:
if( inputContext != IntPtr.Zero ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmReleaseContext(" + handle + ", " + inputContext + ")" );
UnsafeNativeMethods.ImmReleaseContext( new HandleRef( null, handle ), new HandleRef( null, inputContext ) );
}
ImeContext.TraceImeStatus( handle );
Debug.Unindent();
return retval;
}
/// <devdoc>
/// Get the actual IME status - This method is for debugging purposes only.
/// </devdoc>
[Conditional( "DEBUG" )]
internal static void TraceImeStatus( Control ctl ) {
#if DEBUG
if ( ctl.IsHandleCreated ){
TraceImeStatus( ctl.Handle );
}
#endif
}
[Conditional( "DEBUG" )]
private static void TraceImeStatus( IntPtr handle ) {
#if DEBUG
if (CompModSwitches.ImeMode.Level >= TraceLevel.Info) {
string status = "?";
IntPtr inputContext = IntPtr.Zero;
ImeMode[] countryTable = ImeModeConversion.InputLanguageTable;
if (countryTable == ImeModeConversion.UnsupportedTable) {
status = "IME not supported in current language.";
goto cleanup;
}
inputContext = UnsafeNativeMethods.ImmGetContext(new HandleRef(null, handle));
if (inputContext == IntPtr.Zero) {
status = string.Format(CultureInfo.CurrentCulture, "No ime context for handle=[{0}]", handle);
goto cleanup;
}
if (!UnsafeNativeMethods.ImmGetOpenStatus(new HandleRef(null, inputContext))) {
status = string.Format(CultureInfo.CurrentCulture, "Ime closed for handle=[{0}]", handle);
goto cleanup;
}
int conversion = 0; // Combination of conversion mode values
int sentence = 0; // Sentence mode value
UnsafeNativeMethods.ImmGetConversionStatus(new HandleRef(null, inputContext), ref conversion, ref sentence);
ImeMode retval;
if ((conversion & NativeMethods.IME_CMODE_NATIVE) != 0) {
if ((conversion & NativeMethods.IME_CMODE_KATAKANA) != 0) {
retval = ((conversion & NativeMethods.IME_CMODE_FULLSHAPE) != 0)
? countryTable[ImeModeConversion.ImeNativeFullKatakana]
: countryTable[ImeModeConversion.ImeNativeHalfKatakana];
}
else { // ! Katakana
retval = ((conversion & NativeMethods.IME_CMODE_FULLSHAPE) != 0)
? countryTable[ImeModeConversion.ImeNativeFullHiragana]
: countryTable[ImeModeConversion.ImeNativeHalfHiragana];
}
}
else { // ! IME_CMODE_NATIVE
retval = ((conversion & NativeMethods.IME_CMODE_FULLSHAPE) != 0)
? countryTable[ImeModeConversion.ImeAlphaFull]
: countryTable[ImeModeConversion.ImeAlphaHalf];
}
status = string.Format(CultureInfo.CurrentCulture, "Ime conversion status mode for handle=[{0}]: {1}", handle, retval);
cleanup:
if (inputContext != IntPtr.Zero) {
UnsafeNativeMethods.ImmReleaseContext(new HandleRef(null, handle), new HandleRef(null, inputContext));
}
Debug.WriteLine(status);
}
#endif
}
/// <devdoc>
/// Returns true if the IME is currently open
/// </devdoc>
public static bool IsOpen( IntPtr handle ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside ImeContext.IsOpen(" + handle + ")" );
Debug.Indent();
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetContext(" + handle + ")" );
IntPtr inputContext = UnsafeNativeMethods.ImmGetContext( new HandleRef( null, handle ) );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "context = " + inputContext );
bool retval = false;
if( inputContext != IntPtr.Zero ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetOpenStatus(" + inputContext + ")" );
retval = UnsafeNativeMethods.ImmGetOpenStatus( new HandleRef( null, inputContext ) );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmReleaseContext(" + handle + ", " + inputContext + ")" );
UnsafeNativeMethods.ImmReleaseContext( new HandleRef( null, handle ), new HandleRef( null, inputContext ) );
}
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, " IsOpen = " + retval );
Debug.Unindent();
return retval;
}
/// <devdoc>
/// Sets the actual IME context value.
/// </devdoc>
public static void SetImeStatus( ImeMode imeMode, IntPtr handle ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format( CultureInfo.CurrentCulture, "Inside ImeContext.SetImeStatus(ImeMode=[{0}, handle=[{1}])", imeMode, handle ) );
Debug.Indent();
Debug.Assert( imeMode != ImeMode.Inherit, "ImeMode.Inherit is an invalid argument to ImeContext.SetImeStatus" );
if( imeMode == ImeMode.Inherit || imeMode == ImeMode.NoControl ) {
goto cleanup; // No action required
}
ImeMode[] inputLanguageTable = ImeModeConversion.InputLanguageTable;
if (inputLanguageTable == ImeModeConversion.UnsupportedTable) {
goto cleanup; // We only support Japanese, Korean and Chinese IME.
}
int conversion = 0; // Combination of conversion mode values
int sentence = 0; // Sentence mode value
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Warning, "\tSetting IME context to " + imeMode );
if( imeMode == ImeMode.Disable ) {
ImeContext.Disable( handle );
}
else {
// This will make sure the IME is opened.
ImeContext.Enable( handle );
}
switch( imeMode ) {
case ImeMode.NoControl:
case ImeMode.Disable:
break; // No action required
case ImeMode.On:
// IME active mode (CHN = On, JPN = Hiragana, KOR = Hangul).
// Setting ImeMode to Hiragana (or any other active value) will force the IME to get to an active value
// independent on the language.
imeMode = ImeMode.Hiragana;
goto default;
case ImeMode.Off:
// IME direct input (CHN = Off, JPN = Off, KOR = Alpha).
if (inputLanguageTable == ImeModeConversion.JapaneseTable) {
// Japanese IME interprets Close as Off.
goto case ImeMode.Close;
}
// CHN: to differentiate between Close and Off we set the ImeMode to Alpha.
imeMode = ImeMode.Alpha;
goto default;
case ImeMode.Close:
if (inputLanguageTable == ImeModeConversion.KoreanTable) {
// Korean IME has no idea what Close means.
imeMode = ImeMode.Alpha;
goto default;
}
ImeContext.SetOpenStatus( false, handle );
break;
default:
if( ImeModeConversion.ImeModeConversionBits.ContainsKey( imeMode ) ) {
// Update the conversion status
//
ImeModeConversion conversionEntry = (ImeModeConversion) ImeModeConversion.ImeModeConversionBits[imeMode];
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetContext(" + handle + ")" );
IntPtr inputContext = UnsafeNativeMethods.ImmGetContext( new HandleRef( null, handle ) );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "context = " + inputContext );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetConversionStatus(" + inputContext + ", conversion, sentence)" );
UnsafeNativeMethods.ImmGetConversionStatus( new HandleRef( null, inputContext ), ref conversion, ref sentence );
conversion |= conversionEntry.setBits;
conversion &= ~conversionEntry.clearBits;
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmSetConversionStatus(" + inputContext + ", conversion, sentence)" );
bool retval = UnsafeNativeMethods.ImmSetConversionStatus( new HandleRef( null, inputContext ), conversion, sentence );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmReleaseContext(" + handle + ", " + inputContext + ")" );
UnsafeNativeMethods.ImmReleaseContext( new HandleRef( null, handle ), new HandleRef( null, inputContext ) );
}
break;
}
cleanup:
ImeContext.TraceImeStatus( handle );
Debug.Unindent();
}
/// <devdoc>
/// Opens or closes the IME context.
/// </devdoc>
public static void SetOpenStatus( bool open, IntPtr handle ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Info, string.Format( CultureInfo.CurrentCulture, "Inside SetOpenStatus(open=[{0}], handle=[{1}])", open, handle ) );
Debug.Indent();
if( ImeModeConversion.InputLanguageTable != ImeModeConversion.UnsupportedTable ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmGetContext(" + handle + ")" );
IntPtr inputContext = UnsafeNativeMethods.ImmGetContext( new HandleRef( null, handle ) );
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "context = " + inputContext );
if( inputContext != IntPtr.Zero ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmSetOpenStatus(" + inputContext + ", " + open + ")" );
bool succeeded = UnsafeNativeMethods.ImmSetOpenStatus( new HandleRef( null, inputContext ), open );
Debug.Assert( succeeded, "Could not set the IME open status." );
if( succeeded ) {
Debug.WriteLineIf( CompModSwitches.ImeMode.Level >= TraceLevel.Verbose, "ImmReleaseContext(" + handle + ", " + inputContext + ")" );
succeeded = UnsafeNativeMethods.ImmReleaseContext( new HandleRef( null, handle ), new HandleRef( null, inputContext ) );
Debug.Assert( succeeded, "Could not release IME context." );
}
}
}
ImeContext.TraceImeStatus( handle );
Debug.Unindent();
}
}// end ImeContext class
///////////////////////////////////////////////////////// ImeModeConversion structure /////////////////////////////////////////////////////////
/// <devdoc>
/// Helper class that provides information about IME convertion mode. Convertion mode refers to how IME interprets input like
/// ALPHANUMERIC or HIRAGANA and depending on its value the IME enables/disables the IME convertion window appropriately.
/// </devdoc>
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] // this class has no public instance memebers.
public struct ImeModeConversion
{
private static Dictionary<ImeMode, ImeModeConversion> imeModeConversionBits;
internal int setBits;
internal int clearBits;
// Tables of conversions from IME context bits to IME mode
//
//internal const int ImeNotAvailable = 0;
internal const int ImeDisabled = 1;
internal const int ImeDirectInput = 2;
internal const int ImeClosed = 3;
internal const int ImeNativeInput = 4;
internal const int ImeNativeFullHiragana = 4; // Index of Native Input Mode.
internal const int ImeNativeHalfHiragana = 5;
internal const int ImeNativeFullKatakana = 6;
internal const int ImeNativeHalfKatakana = 7;
internal const int ImeAlphaFull = 8;
internal const int ImeAlphaHalf = 9;
/// <devdoc>
/// Supported input language ImeMode tables.
/// WARNING: Do not try to map 'active' IME modes from one table to another since they can have a different
/// meaning depending on the language; for instance ImeMode.Off means 'disable' or 'alpha' to Chinese
/// but to Japanese it is 'alpha' and to Korean it has no meaning.
/// </devdoc>
private static ImeMode[] japaneseTable = {
ImeMode.Inherit,
ImeMode.Disable,
ImeMode.Off,
ImeMode.Off,
ImeMode.Hiragana,
ImeMode.Hiragana,
ImeMode.Katakana,
ImeMode.KatakanaHalf,
ImeMode.AlphaFull,
ImeMode.Alpha
};
private static ImeMode[] koreanTable = {
ImeMode.Inherit,
ImeMode.Disable,
ImeMode.Alpha,
ImeMode.Alpha,
ImeMode.HangulFull,
ImeMode.Hangul,
ImeMode.HangulFull,
ImeMode.Hangul,
ImeMode.AlphaFull,
ImeMode.Alpha
};
private static ImeMode[] chineseTable = {
ImeMode.Inherit,
ImeMode.Disable,
ImeMode.Off,
ImeMode.Close,
ImeMode.On,
ImeMode.OnHalf,
ImeMode.On,
ImeMode.OnHalf,
ImeMode.Off,
ImeMode.Off
};
private static ImeMode[] unsupportedTable = {
};
internal static ImeMode[] ChineseTable {
get {
return chineseTable;
}
}
internal static ImeMode[] JapaneseTable {
get {
return japaneseTable;
}
}
internal static ImeMode[] KoreanTable {
get {
return koreanTable;
}
}
internal static ImeMode[] UnsupportedTable {
get {
return unsupportedTable;
}
}
/// <devdoc>
/// Gets the ImeMode table of the current input language.
/// Although this property is per-thread based we cannot cache it and share it among controls running in the same thread
/// for two main reasons: we still have some controls that don't handle IME properly (TabControl, ComboBox, TreeView...)
/// and would render it invalid and since the IME API is not public third party controls would not have a way to update
/// the cached value. See VSW#541650/541728.
/// </devdoc>
internal static ImeMode[] InputLanguageTable {
get {
Debug.WriteLineIf(CompModSwitches.ImeMode.Level >= TraceLevel.Info, "Inside get_InputLanguageTable(), Input Language = " + InputLanguage.CurrentInputLanguage.Culture.DisplayName + ", Thread = " + System.Threading.Thread.CurrentThread.ManagedThreadId);
InputLanguage inputLanguage = InputLanguage.CurrentInputLanguage;
int lcid = (int)((long)inputLanguage.Handle & (long)0xFFFF);
switch (lcid) {
case 0x0404: // Chinese (----)
case 0x0804: // Chinese (PRC)
case 0x0c04: // Chinese (Hong Kong SAR, PRC)
case 0x1004: // Chinese (Singapore)
case 0x1404: // Chinese (Macau SAR)
return chineseTable;
case 0x0412: // Korean
case 0x0812: // Korean (Johab)
return koreanTable;
case 0x0411: // Japanese
return japaneseTable;
default:
return unsupportedTable;
}
}
}
/// <devdoc>
/// Dictionary of ImeMode and corresponding convertion flags.
/// </devdoc>
public static Dictionary<ImeMode, ImeModeConversion> ImeModeConversionBits {
get {
if( imeModeConversionBits == null ) {
// Create ImeModeConversionBits dictionary
imeModeConversionBits = new Dictionary<ImeMode, ImeModeConversion>( 7 );
ImeModeConversion conversion;
// Hiragana, On
//
conversion.setBits = NativeMethods.IME_CMODE_FULLSHAPE | NativeMethods.IME_CMODE_NATIVE;
conversion.clearBits = NativeMethods.IME_CMODE_KATAKANA;
imeModeConversionBits.Add( ImeMode.Hiragana, conversion );
// Katakana
//
conversion.setBits = NativeMethods.IME_CMODE_FULLSHAPE | NativeMethods.IME_CMODE_KATAKANA | NativeMethods.IME_CMODE_NATIVE;
conversion.clearBits = 0;
imeModeConversionBits.Add( ImeMode.Katakana, conversion );
// KatakanaHalf
//
conversion.setBits = NativeMethods.IME_CMODE_KATAKANA | NativeMethods.IME_CMODE_NATIVE;
conversion.clearBits = NativeMethods.IME_CMODE_FULLSHAPE;
imeModeConversionBits.Add( ImeMode.KatakanaHalf, conversion );
// AlphaFull
//
conversion.setBits = NativeMethods.IME_CMODE_FULLSHAPE;
conversion.clearBits = NativeMethods.IME_CMODE_KATAKANA | NativeMethods.IME_CMODE_NATIVE;
imeModeConversionBits.Add( ImeMode.AlphaFull, conversion );
// Alpha
//
conversion.setBits = 0;
conversion.clearBits = NativeMethods.IME_CMODE_FULLSHAPE | NativeMethods.IME_CMODE_KATAKANA | NativeMethods.IME_CMODE_NATIVE;
imeModeConversionBits.Add( ImeMode.Alpha, conversion );
// HangulFull
//
conversion.setBits = NativeMethods.IME_CMODE_FULLSHAPE | NativeMethods.IME_CMODE_NATIVE;
conversion.clearBits = 0;
imeModeConversionBits.Add( ImeMode.HangulFull, conversion );
// Hangul
//
conversion.setBits = NativeMethods.IME_CMODE_NATIVE;
conversion.clearBits = NativeMethods.IME_CMODE_FULLSHAPE;
imeModeConversionBits.Add( ImeMode.Hangul, conversion );
// OnHalf
//
conversion.setBits = NativeMethods.IME_CMODE_NATIVE;
conversion.clearBits = NativeMethods.IME_CMODE_KATAKANA | NativeMethods.IME_CMODE_FULLSHAPE;
imeModeConversionBits.Add(ImeMode.OnHalf, conversion);
}
return imeModeConversionBits;
}
}
public static bool IsCurrentConversionTableSupported{
get {
return InputLanguageTable != UnsupportedTable;
}
}
}// end ImeModeConversion struct.
} // end namespace System.Windows.Forms
|