|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Specialized; // NameValueCollection
using System.Configuration; // ConfigurationManager
namespace System.Windows
{
public static class FrameworkCompatibilityPreferences
{
#region Constructor
static FrameworkCompatibilityPreferences()
{
#if NETFX && !NETCOREAPP
_targetsDesktop_V4_0 = BinaryCompatibility.AppWasBuiltForFramework == TargetFrameworkId.NetFramework
&& !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5;
#elif NETCOREAPP
// When building for NETCOREAPP, set this to false
// to indicate that quirks should be treated as if they are running on
// .NET 4.5+
_targetsDesktop_V4_0 = false;
#else
_targetsDesktop_V4_0 = false;
#endif
// user can use config file to set preferences
NameValueCollection appSettings = null;
try
{
appSettings = ConfigurationManager.AppSettings;
}
catch (ConfigurationErrorsException)
{
}
if (appSettings != null)
{
SetUseSetWindowPosForTopmostWindowsFromAppSettings(appSettings);
SetVSP45CompatFromAppSettings(appSettings);
SetScrollingTraceFromAppSettings(appSettings);
SetShouldThrowOnCopyOrCutFailuresFromAppSettings(appSettings);
SetIMECompositionTraceFromAppSettings(appSettings);
}
}
#endregion Constructor
#region TargetsDesktop_V4_0
// CLR's BinaryCompatibility class doesn't expose a convenient way to determine
// if the app targets 4.0 exactly. We use that a lot, so encapsulate it here
static bool _targetsDesktop_V4_0;
internal static bool TargetsDesktop_V4_0
{
get { return _targetsDesktop_V4_0; }
}
#endregion TargetsDesktop_V4_0
#region AreInactiveSelectionHighlightBrushKeysSupported
#if NETFX && !NETCOREAPP
private static bool _areInactiveSelectionHighlightBrushKeysSupported = BinaryCompatibility.TargetsAtLeast_Desktop_V4_5 ? true : false;
#elif NETCOREAPP
private static bool _areInactiveSelectionHighlightBrushKeysSupported = true;
#else
private static bool _areInactiveSelectionHighlightBrushKeysSupported = true;
#endif
public static bool AreInactiveSelectionHighlightBrushKeysSupported
{
get { return _areInactiveSelectionHighlightBrushKeysSupported; }
set
{
lock (_lockObject)
{
if (_isSealed)
{
throw new InvalidOperationException(SR.Format(SR.CompatibilityPreferencesSealed, "AreInactiveSelectionHighlightBrushKeysSupported", "FrameworkCompatibilityPreferences"));
}
_areInactiveSelectionHighlightBrushKeysSupported = value;
}
}
}
internal static bool GetAreInactiveSelectionHighlightBrushKeysSupported()
{
Seal();
return AreInactiveSelectionHighlightBrushKeysSupported;
}
#endregion AreInactiveSelectionHighlightBrushKeysSupported
#region KeepTextBoxDisplaySynchronizedWithTextProperty
#if NETFX && !NETCOREAPP
private static bool _keepTextBoxDisplaySynchronizedWithTextProperty = BinaryCompatibility.TargetsAtLeast_Desktop_V4_5 ? true : false;
#elif NETCOREAPP
private static bool _keepTextBoxDisplaySynchronizedWithTextProperty = true;
#else
private static bool _keepTextBoxDisplaySynchronizedWithTextProperty = true;
#endif
/// <summary>
/// In WPF 4.0, a TextBox can reach a state where its Text property
/// has some value X, but a different value Y is displayed. Setting
/// FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty=true
/// ensures that the displayed value always agrees with the value of the Text property.
///
/// </summary>
/// <notes>
/// The inconsistent state can be reached as follows:
/// 1. The TextBox is data-bound with property-changed update trigger
/// <TextBox Text="{Binding UpdateSourceTrigger=PropertyChanged, Path=ABC}"/>
/// 2. The TextBox displays a value X
/// 3. The user types a character to produce a new value Y
/// 4. The new value Y is sent to the data item's property ABC then read
/// back again, possibly applying conversions in each direction. The
/// data item may "normalize" the value as well - upon receiving value V it
/// may store a different value V'. Denote by Z the result of this round-trip.
/// 5. The Text property is set to Z.
/// 6. Usually the text box will now display Z. But if Z and X are the same,
/// it will display Y (which is different from X).
///
/// For example, suppose the data item normalizes by trimming spaces:
/// public string ABC { set { _abc = value.Trim(); } }
/// And suppose the user types "hi ". Upon typing the space, the binding
/// sends "hi " to the data item, which stores "hi". The result of the round-trip
/// is "hi", which is identical to the string before typing the space. In
/// this case, the TextBox reaches a state where its Text property has value
/// "hi" although it displays "hi ".
///
/// As a second example, suppose the data item normalizes an integer by
/// capping its value to a maximum, say 100:
/// public int Score { set { _score = Math.Min(value, 100); } }
/// And suppose the user types "1004". Upon typing the 4, the binding converts
/// string "1004" to int 1004 and sends 1004 to the data item, which stores 100.
/// The round-trip continues, converting int 100 to string "100", which is
/// identical to the text before typing the 4. The TextBox reaches a state
/// where its Text property has value "100", but it displays "1004".
/// </notes>
public static bool KeepTextBoxDisplaySynchronizedWithTextProperty
{
get { return _keepTextBoxDisplaySynchronizedWithTextProperty; }
set
{
lock (_lockObject)
{
if (_isSealed)
{
throw new InvalidOperationException(SR.Format(SR.CompatibilityPreferencesSealed, "AextBoxDisplaysText", "FrameworkCompatibilityPreferences"));
}
_keepTextBoxDisplaySynchronizedWithTextProperty = value;
}
}
}
internal static bool GetKeepTextBoxDisplaySynchronizedWithTextProperty()
{
Seal();
return KeepTextBoxDisplaySynchronizedWithTextProperty;
}
#endregion KeepTextBoxDisplaySynchronizedWithTextProperty
// There is a bug in the Windows desktop window manager which can cause
// incorrect z-order for windows when several conditions are all met:
// (a) windows are parented/owned across different threads or processes
// (b) a parent/owner window is also owner of a topmost window (which needn't be visible)
// (c) the child window on a different thread/process tries to show an owned topmost window
// (like a popup or tooltip) using ShowWindow().
// To avoid this window manager bug, this option causes SetWindowPos() to be used instead of
// ShowWindow() for topmost windows, avoiding condition (c). Ideally the window manager bug
// will be fixed, but the risk of making a change there is considered too great at this time.
#region UseSetWindowPosForTopmostWindows
private static bool _useSetWindowPosForTopmostWindows = false; // use old behavior by default
internal static bool UseSetWindowPosForTopmostWindows
{
get { return _useSetWindowPosForTopmostWindows; }
set
{
lock (_lockObject)
{
if (_isSealed)
{
throw new InvalidOperationException(SR.Format(SR.CompatibilityPreferencesSealed, "UseSetWindowPosForTopmostWindows", "FrameworkCompatibilityPreferences"));
}
_useSetWindowPosForTopmostWindows = value;
}
}
}
internal static bool GetUseSetWindowPosForTopmostWindows()
{
Seal();
return UseSetWindowPosForTopmostWindows;
}
static void SetUseSetWindowPosForTopmostWindowsFromAppSettings(NameValueCollection appSettings)
{
// user can use config file to enable this behavior change
string s = appSettings["UseSetWindowPosForTopmostWindows"];
bool useSetWindowPos;
if (Boolean.TryParse(s, out useSetWindowPos))
{
UseSetWindowPosForTopmostWindows = useSetWindowPos;
}
}
#endregion UseSetWindowPosForTopmostWindows
#region VSP45Compat
// VirtualizingStackPanel added support for virtualization-when-grouping in 4.5,
// generalizing and subsuming the support for virtualizing a TreeView that existed in 4.0.
// The 4.5 algorithm had many flaws, leading to infinite loops, scrolling
// to the wrong place, and other bad symptoms. DDCC is worried that fixing
// these issues may introduce new compat problems, and asked for a way to opt out
// of the fixes. To opt out, add an entry to the <appSettings> section of the
// app config file:
// <add key="IsVirtualizingStackPanel_45Compatible" value="true"/>
private static bool _vsp45Compat = false;
internal static bool VSP45Compat
{
get { return _vsp45Compat; }
set
{
lock (_lockObject)
{
if (_isSealed)
{
throw new InvalidOperationException(SR.Format(SR.CompatibilityPreferencesSealed, "IsVirtualizingStackPanel_45Compatible", "FrameworkCompatibilityPreferences"));
}
_vsp45Compat = value;
}
}
}
internal static bool GetVSP45Compat()
{
Seal();
return VSP45Compat;
}
static void SetVSP45CompatFromAppSettings(NameValueCollection appSettings)
{
// user can use config file to opt out of VSP fixes
string s = appSettings["IsVirtualizingStackPanel_45Compatible"];
bool value;
if (Boolean.TryParse(s, out value))
{
VSP45Compat = value;
}
}
#endregion VSP45Compat
#region ScrollingTrace
private static string _scrollingTraceTarget;
internal static string GetScrollingTraceTarget()
{
Seal();
return _scrollingTraceTarget;
}
private static string _scrollingTraceFile;
internal static string GetScrollingTraceFile()
{
Seal();
return _scrollingTraceFile;
}
static void SetScrollingTraceFromAppSettings(NameValueCollection appSettings)
{
// user can use config file to select a control (TreeView, DataGrid, etc.)
// for in-flight tracing of scrolling behavior:
// <add key="ScrollingTraceTarget" value="NameOfControl"/>
_scrollingTraceTarget = appSettings["ScrollingTraceTarget"];
// user can direct scroll-tracing output to a file:
// <add key="ScrollingTraceFile" value="NameOfFile"/>
// If the key is not present, or the filename is absent or "default",
// the output goes to "ScrollTrace.stf". If the filename is "none",
// no file output is produced.
//
// User can also specify a parameter to control when output is flushed
// to the file:
// <add key="ScrollingTraceFile" value="NameOfFile;nnn"/>
// If not specified, the output is flushed after completing Measure or
// Arrange of the top-level VirtualizingStackPanel below the trace
// target. In some scenarios it may be desirable to flush the output
// more often - for example, an infinite loop that never measures the
// top-level panel. Use the optional nnn parameter to flush after
// Measure or Arrange of any panel whose depth is nnn or less. This flushes
// more often, but is more likely to interfere with the timing of the app.
_scrollingTraceFile = appSettings["ScrollingTraceFile"];
// Alternatively, the user can control tracing from the VS debugger.
// To enable tracing:
// 1. Locate the desired control (TreeView, DataGrid, etc.) and
// make an Object ID for it.
// 2. From the Immediate window, execute
// VirtualizingStackPanel.ScrollTracer.SetTarget(1#)
// (using the appropriate ID instead of 1#)
// To control the file output
// 1. From the Immediate window, execute
// VirtualizaingStackPanel.ScrollTracer.SetFileAndDepth("filename", n)
// to specify the file and the desired flushing depth.
// To flush the current trace data to the file (useful if the app is
// about to terminate - including force-termination from the debugger
// or TaskManager - but you want to capture the latest trace data):
// 1. From the Immediate window, execute
// VirtualizaingStackPanel.ScrollTracer.Flush()
}
#endregion ScrollingTrace
#region ShouldThrowOnCopyOrCutFailure
private static bool _shouldThrowOnCopyOrCutFailure = false;
/// <summary>
/// When True, a failed Copy or Cut operation in a TextBoxBase instance will result in
/// a <see cref="System.Runtime.InteropServices.ExternalException"/>.
/// When False (default), a failed Copy or Cut operation will be silently ignored.
/// </summary>
/// <remarks>
/// When a clipboard operation fails,for e.g., with HRESULT 0x800401D0 (CLIPBRD_E_CANT_OPEN),
/// a corresponding <see cref="System.Runtime.InteropServices.COMException"/> (which is a type of
/// ExternalException) is thrown.
///
/// The Win32 OpenClipboard API acts globally, and the corresponding
/// CloseClipboard call should be made by well written applications as soon as they have
/// completed their clipboard operations. When an application calls OpenClipboard and then fails
/// to call CloseClipboard, it results in all other applications running the same session
/// being unable to access clipboard functions.
///
/// In WPF, such a denial of access to clipboard is
/// normally ignored silently. Applications can opt into receiving an ExternalException upon
/// failure by setting this flag. Opting to receive exceptions requires that
/// the application would take control of handling <see cref="System.Windows.Input.ApplicationCommands.Cut"/>
/// and <see cref="System.Windows.Input.ApplicationCommands.Copy"/> RoutedUICommands through a
/// <see cref="System.Windows.Input.CommandBinding"/>, and apply that binding to all TextBoxBase
/// controls (<see cref="System.Windows.Controls.TextBox"/> and <see cref="System.Windows.Controls.RichTextBox"/>)
/// in the application. The application should ensure that it handles ExternalExeptions arising from Copy/Cut
/// operations in the CommandBinding's Executed handler.
/// </remarks>
public static bool ShouldThrowOnCopyOrCutFailure
{
get
{
return _shouldThrowOnCopyOrCutFailure;
}
set
{
if (_isSealed)
{
throw new InvalidOperationException(
SR.Format(SR.CompatibilityPreferencesSealed,
nameof(ShouldThrowOnCopyOrCutFailure),
nameof(FrameworkCompatibilityPreferences)));
}
_shouldThrowOnCopyOrCutFailure = value;
}
}
internal static bool GetShouldThrowOnCopyOrCutFailure()
{
Seal();
return ShouldThrowOnCopyOrCutFailure;
}
static void SetShouldThrowOnCopyOrCutFailuresFromAppSettings(NameValueCollection appSettings)
{
// user can use config file to enable this behavior change
string s = appSettings[nameof(ShouldThrowOnCopyOrCutFailure)];
bool shouldThrowOnCopyOrCutFailure;
if (Boolean.TryParse(s, out shouldThrowOnCopyOrCutFailure))
{
ShouldThrowOnCopyOrCutFailure = shouldThrowOnCopyOrCutFailure;
}
}
#endregion ShouldThrowOnCopyOrCutFailure
#region IMECompositionTrace
private static string _IMECompositionTraceTarget;
internal static string GetIMECompositionTraceTarget()
{
Seal();
return _IMECompositionTraceTarget;
}
private static string _IMECompositionTraceFile;
internal static string GetIMECompositionTraceFile()
{
Seal();
return _IMECompositionTraceFile;
}
static void SetIMECompositionTraceFromAppSettings(NameValueCollection appSettings)
{
// user can use config file to select a control (TextBox, RichTextBox, etc.)
// for in-flight tracing of IME composition behavior:
// <add key="IMECompositionTraceTarget" value="NameOfControl"/>
_IMECompositionTraceTarget = appSettings["IMECompositionTraceTarget"];
// user can direct IMEComposition-tracing output to a file:
// <add key="IMECompositionTraceFile" value="NameOfFile"/>
// If the key is not present, or the filename is absent or "default",
// the output goes to "IMECompositionTrace.stf". If the filename is "none",
// no file output is produced.
//
// User can also specify a parameter to control when output is flushed
// to the file:
// <add key="IMECompositionTraceFile" value="NameOfFile;nnn"/>
// If not specified, the output is flushed after completing Measure or TODO-rewrite
// Arrange of the top-level VirtualizingStackPanel below the trace
// target. In some scenarios it may be desirable to flush the output
// more often - for example, an infinite loop that never measures the
// top-level panel. Use the optional nnn parameter to flush after
// Measure or Arrange of any panel whose depth is nnn or less. This flushes
// more often, but is more likely to interfere with the timing of the app.
_IMECompositionTraceFile = appSettings["IMECompositionTraceFile"];
// Alternatively, the user can control tracing from the VS debugger.
// To enable tracing:
// 1. Locate the desired control (TextBox, RichTextBox, etc.) and
// make an Object ID for it.
// 2. From the Immediate window, execute
// TextStore.IMECompositionTracer.SetTarget(1#)
// (using the appropriate ID instead of 1#)
// To flush the current trace data to the file (useful if the app is
// about to terminate - including force-termination from the debugger
// or TaskManager - but you want to capture the latest trace data):
// 1. From the Immediate window, execute
// TextStore.IMECompositionTracer.Flush()
}
#endregion IMECompositionTrace
private static void Seal()
{
if (!_isSealed)
{
lock (_lockObject)
{
_isSealed = true;
}
}
}
private static bool _isSealed;
private static readonly object _lockObject = new object();
}
}
|