首先,本文所有 代码已经提交到github,需要的可以直接从github获取:https://github.com/starts2000/CefSharp,希望可以帮助到有需要的朋友们。

CEF 简介

CEF is a BSD-licensed open source project founded by Marshall Greenblatt in 2008 and based on the Google Chromium project. Unlike the Chromium project itself, which focuses mainly on Google Chrome application development, CEF focuses on facilitating embedded browser use cases in third-party applications. CEF insulates the user from the underlying Chromium and Blink code complexity by offering production-quality stable APIs, release branches tracking specific Chromium releases, and binary distributions. Most features in CEF have default implementations that provide rich functionality while requiring little or no integration work from the user. There are currently over 100 million installed instances of CEF around the world embedded in products from a wide range of companies and industries. A partial list of companies and products using CEF is available on the CEF Wikipedia page. Some use cases for CEF include:

  • Embedding an HTML5-compliant Web browser control in an existing native application.
  • Creating a light-weight native “shell” application that hosts a user interface developed primarily using Web technologies.
  • Rendering Web content “off-screen” in applications that have their own custom drawing frameworks.
  • Acting as a host for automated testing of existing Web properties and applications.

CEF supports a wide range of programming languages and operating systems and can be easily integrated into both new and existing applications. It was designed from the ground up with both performance and ease of use in mind. The base framework includes C and C++ programming interfaces exposed via native libraries that insulate the host application from Chromium and Blink implementation details. It provides close integration between the browser and the host application including support for custom plugins, protocols, JavaScript objects and JavaScript extensions. The host application can optionally control resource loading, navigation, context menus, printing and more, while taking advantage of the same performance and HTML5 technologies available in the Google Chrome Web browser.
Numerous individuals and organizations contribute time and resources to support CEF development, but more involvement from the community is always welcome. This includes support for both the core CEF project and external projects that integrate CEF with additional programming languages and frameworks (see the "External Projects" section below). If you are interested in donating time to help with CEF development please see the "Helping Out" section below. If you are interested in donating money to support general CEF development and infrastructure efforts please visit the CEF Donations page.

CEF 的 .NET 开源项目主要有下面三个:

  1. CefSharp:https://github.com/chillitom/CefSharp
  2. cefglue:https://bitbucket.org/xilium/xilium.cefglue
  3. chromiumfx:https://bitbucket.org/chromiumfx/chromiumfx

CEF osr IME BUG 历史

CefSharp 和 cefglue 都使用 C++/CLI  对 cef API 进行封装,都提供了 cef 的 Winform 和 WPF 控件,而 chromiumfx 使用 P/Invoke 对 cef API 进行封装,只提供了cef Winform 控件。CefSharp 和 cefglue 的  cef WPF 控件都使用 cef 的 osr ( off screen  render)方式进行渲染,由于 osr 模式一直存在 IME BUG,所以 CefSharp 和 cefglue 的 WPF 控件也存在。

CEF osr IME bug 在 2012-11-22 就有人提出:https://bitbucket.org/chromiumembedded/cef/issues/798/out-of-focus-while-entering-ime,但是直到2016年底才解决https://bitbucket.org/chromiumembedded/cef/issues/1675/inline-ime-support-nstextinput-protocol-in,真是等的黄花菜都凉了。然而, CefSharp 和 cefglue 更是没能跟上 CEF 的脚步,这个 BUG 直到现在也没有解决,所有相关的 issue,回答都是建议在 WPF 中使用 Host WinForm 控件的方式使用 CEF。

CEF osr IME bug:

最近通过参考 cef 的 osr 示例的源码,通过移植和修改,终于实现了 CefSharp WPF 控件的 IME 输入,在这里分享给大家。

主要是在 CefSharp.Core 项目中增加了对 IME 消息及 CEF IME 相关的处理,还有就是 WPF 的 ChromiumWebBrowser 控件的相关代码修改。

1.OsrImeHandler.cpp

// Copyright 2016 The Chromium Embedded Framework Authors. Portions copyright
// 2013 The Chromium Authors. All rights reserved. Use of this source code is
// governed by a BSD-style license that can be found in the LICENSE file. // Implementation based on ui/base/ime/win/imm32_manager.cc from Chromium. #include "Stdafx.h"
//#include <windowsx.h>
//#include <msctf.h> #include "include/base/cef_build.h"
#include "OsrImeHandler.h" #pragma comment(lib, "imm32.lib") #define ColorUNDERLINE 0xFF000000 // Black SkColor value for underline,
// same as Blink.
#define ColorBKCOLOR 0x00000000 // White SkColor value for background,
// same as Blink. namespace CefSharp { namespace { // Determines whether or not the given attribute represents a selection
bool IsSelectionAttribute(char attribute) {
return (attribute == ATTR_TARGET_CONVERTED ||
attribute == ATTR_TARGET_NOTCONVERTED);
} // Helper function for OsrImeHandler::GetCompositionInfo() method,
// to get the target range that's selected by the user in the current
// composition string.
void GetCompositionSelectionRange(HIMC imc, int* target_start,
int* target_end) {
int attribute_size = ::ImmGetCompositionString(imc, GCS_COMPATTR, NULL, );
if (attribute_size > ) {
int start = ;
int end = ;
std::vector<char> attribute_data(attribute_size); ::ImmGetCompositionString(imc, GCS_COMPATTR, &attribute_data[],
attribute_size);
for (start = ; start < attribute_size; ++start) {
if (IsSelectionAttribute(attribute_data[start]))
break;
}
for (end = start; end < attribute_size; ++end) {
if (!IsSelectionAttribute(attribute_data[end]))
break;
} *target_start = start;
*target_end = end;
}
} // Helper function for OsrImeHandler::GetCompositionInfo() method, to get
// underlines information of the current composition string.
void GetCompositionUnderlines(
HIMC imc,
int target_start,
int target_end,
std::vector<CefCompositionUnderline> &underlines) {
int clause_size = ::ImmGetCompositionString(imc, GCS_COMPCLAUSE, NULL, );
int clause_length = clause_size / sizeof(uint32);
if (clause_length) {
std::vector<uint32> clause_data(clause_length); ::ImmGetCompositionString(imc, GCS_COMPCLAUSE,
&clause_data[], clause_size);
for (int i = ; i < clause_length - ; ++i) {
cef_composition_underline_t underline;
underline.range.from = clause_data[i];
underline.range.to = clause_data[i + ];
underline.color = ColorUNDERLINE;
underline.background_color = ColorBKCOLOR;
underline.thick = ; // Use thick underline for the target clause.
if (underline.range.from >= target_start &&
underline.range.to <= target_end) {
underline.thick = ;
}
underlines.push_back(underline);
}
}
} } // namespace OsrImeHandler::OsrImeHandler(HWND hwnd)
: ime_status_(false),
hwnd_(hwnd),
input_language_id_(LANG_USER_DEFAULT),
is_composing_(false),
cursor_index_(-),
system_caret_(false) {
ime_rect_ = { -, -, , };
} OsrImeHandler::~OsrImeHandler() {
DestroyImeWindow();
} void OsrImeHandler::SetInputLanguage() {
// Retrieve the current input language from the system's keyboard layout.
// Using GetKeyboardLayoutName instead of GetKeyboardLayout, because
// the language from GetKeyboardLayout is the language under where the
// keyboard layout is installed. And the language from GetKeyboardLayoutName
// indicates the language of the keyboard layout itself.
// See crbug.com/344834.
WCHAR keyboard_layout[KL_NAMELENGTH];
if (::GetKeyboardLayoutNameW(keyboard_layout)) {
input_language_id_ =
static_cast<LANGID>(_wtoi(&keyboard_layout[KL_NAMELENGTH >> ]));
} else {
input_language_id_ = 0x0409; // Fallback to en-US.
}
} void OsrImeHandler::CreateImeWindow() {
// Chinese/Japanese IMEs somehow ignore function calls to
// ::ImmSetCandidateWindow(), and use the position of the current system
// caret instead -::GetCaretPos().
// Therefore, we create a temporary system caret for Chinese IMEs and use
// it during this input context.
// Since some third-party Japanese IME also uses ::GetCaretPos() to determine
// their window position, we also create a caret for Japanese IMEs.
if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE ||
PRIMARYLANGID(input_language_id_) == LANG_JAPANESE) {
if (!system_caret_) {
if (::CreateCaret(hwnd_, NULL, , ))
system_caret_ = true;
}
}
} void OsrImeHandler::DestroyImeWindow() {
// Destroy the system caret if we have created for this IME input context.
if (system_caret_) {
::DestroyCaret();
system_caret_ = false;
}
} void OsrImeHandler::MoveImeWindow() {
// Does nothing when the target window has no input focus.
if (GetFocus() != hwnd_)
return; CefRect rc = ime_rect_;
int location = cursor_index_; // If location is not specified fall back to the composition range start.
if (location == -)
location = composition_range_.from; // Offset location by the composition range start if required.
if (location >= composition_range_.from)
location -= composition_range_.from; if (location < static_cast<int>(composition_bounds_.size()))
rc = composition_bounds_[location];
else
return; HIMC imc = ::ImmGetContext(hwnd_); if (imc) {
const int kCaretMargin = ;
if (PRIMARYLANGID(input_language_id_) == LANG_CHINESE) {
// Chinese IMEs ignore function calls to ::ImmSetCandidateWindow()
// when a user disables TSF (Text Service Framework) and CUAS (Cicero
// Unaware Application Support).
// On the other hand, when a user enables TSF and CUAS, Chinese IMEs
// ignore the position of the current system caret and use the
// parameters given to ::ImmSetCandidateWindow() with its 'dwStyle'
// parameter CFS_CANDIDATEPOS.
// Therefore, we do not only call ::ImmSetCandidateWindow() but also
// set the positions of the temporary system caret if it exists.
/*CANDIDATEFORM candidate_position = {
0, CFS_CANDIDATEPOS, { rc.x, rc.y }, { 0, 0, 0, 0 }
};
::ImmSetCandidateWindow(imc, &candidate_position);*/ COMPOSITIONFORM form = {
CFS_POINT,{ rc.x, rc.y },{ rc.x, rc.y, rc.x + rc.width, rc.y + rc.height }
}; auto ret = ::ImmSetCompositionWindow(imc, &form);
} if (system_caret_) {
switch (PRIMARYLANGID(input_language_id_)) {
case LANG_JAPANESE:
::SetCaretPos(rc.x, rc.y + rc.height);
break;
default:
::SetCaretPos(rc.x, rc.y);
break;
}
} if (PRIMARYLANGID(input_language_id_) == LANG_KOREAN) {
// Korean IMEs require the lower-left corner of the caret to move their
// candidate windows.
rc.y += kCaretMargin;
} COMPOSITIONFORM form = {
CFS_RECT,{ rc.x, rc.y },{ rc.x, rc.y, rc.x + rc.width, rc.y + rc.height }
}; auto ret = ::ImmSetCompositionWindow(imc, &form);
Debug::Assert(ret != );
//Debug::WriteLine("=======ImmSetCompositionWindow:{0}, Rc:[{1},{2}]", ret != 0, rc.x, rc.y); // Japanese IMEs and Korean IMEs also use the rectangle given to
// ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE
// Therefore, we also set this parameter here.
//CANDIDATEFORM exclude_rectangle = {
// 0, CFS_EXCLUDE, { rc.x, rc.y },
// { rc.x, rc.y, rc.x + rc.width, rc.y + rc.height }
//}; //auto ret = ::ImmSetCandidateWindow(imc, &exclude_rectangle);
//Debug::WriteLine("=======ImmSetCandidateWindow:{0}, Rc:[{1},{2}]", ret != 0, rc.x, rc.y);
::ImmReleaseContext(hwnd_, imc);
}
} void OsrImeHandler::CleanupComposition() {
// Notify the IMM attached to the given window to complete the ongoing
// composition (when given window is de-activated while composing and
// re-activated) and reset the composition status.
if (is_composing_) {
HIMC imc = ::ImmGetContext(hwnd_);
if (imc) {
::ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_COMPLETE, );
::ImmReleaseContext(hwnd_, imc);
}
ResetComposition();
}
} void OsrImeHandler::ResetComposition() {
// Reset the composition status.
is_composing_ = false;
cursor_index_ = -;
} void OsrImeHandler::GetCompositionInfo(
HIMC imc,
LPARAM lparam,
CefString &composition_text,
std::vector<CefCompositionUnderline> &underlines,
int& composition_start) {
// We only care about GCS_COMPATTR, GCS_COMPCLAUSE and GCS_CURSORPOS, and
// convert them into underlines and selection range respectively.
underlines.clear(); int length = static_cast<int>(composition_text.length()); // Find out the range selected by the user.
int target_start = length;
int target_end = length;
if (lparam & GCS_COMPATTR)
GetCompositionSelectionRange(imc, &target_start, &target_end); // Retrieve the selection range information. If CS_NOMOVECARET is specified
// it means the cursor should not be moved and we therefore place the caret at
// the beginning of the composition string. Otherwise we should honour the
// GCS_CURSORPOS value if it's available.
// TODO(suzhe): Due to a bug in WebKit we currently can't use selection range
// with composition string.
// See: https://bugs.webkit.org/show_bug.cgi?id=40805
if (!(lparam & CS_NOMOVECARET) && (lparam & GCS_CURSORPOS)) {
// IMM32 does not support non-zero-width selection in a composition. So
// always use the caret position as selection range.
int cursor = ::ImmGetCompositionString(imc, GCS_CURSORPOS, NULL, );
composition_start = cursor;
} else {
composition_start = ;
} // Retrieve the clause segmentations and convert them to underlines.
if (lparam & GCS_COMPCLAUSE)
GetCompositionUnderlines(imc, target_start, target_end, underlines); // Set default underlines in case there is no clause information.
if (!underlines.size()) {
CefCompositionUnderline underline;
underline.color = ColorUNDERLINE;
underline.background_color = ColorBKCOLOR;
if (target_start > ) {
underline.range.from = ;
underline.range.to = target_start;
underline.thick = ;
underlines.push_back(underline);
}
if (target_end > target_start) {
underline.range.from = target_start;
underline.range.to = target_end;
underline.thick = ;
underlines.push_back(underline);
}
if (target_end < length) {
underline.range.from = target_end;
underline.range.to = length;
underline.thick = ;
underlines.push_back(underline);
}
}
} bool OsrImeHandler::GetString(HIMC imc, WPARAM lparam, int type,
CefString& result) {
if (!(lparam & type))
return false;
LONG string_size = ::ImmGetCompositionString(imc, type, NULL, );
if (string_size <= )
return false; // For trailing NULL - ImmGetCompositionString excludes that.
string_size += sizeof(WCHAR); std::vector<wchar_t> buffer(string_size);
::ImmGetCompositionString(imc, type, &buffer[], string_size);
result.FromWString(&buffer[]);
return true;
} bool OsrImeHandler::GetResult(LPARAM lparam, CefString& result) {
bool ret = false;
HIMC imc = ::ImmGetContext(hwnd_);
if (imc) {
ret = GetString(imc, lparam, GCS_RESULTSTR, result);
::ImmReleaseContext(hwnd_, imc);
}
return ret;
} bool OsrImeHandler::GetComposition(
LPARAM lparam,
CefString &composition_text,
std::vector<CefCompositionUnderline> &underlines,
int& composition_start) {
bool ret = false;
HIMC imc = ::ImmGetContext(hwnd_);
if (imc) {
// Copy the composition string to the CompositionText object.
ret = GetString(imc, lparam, GCS_COMPSTR, composition_text); if (ret) {
// Retrieve the composition underlines and selection range information.
GetCompositionInfo(imc, lparam, composition_text, underlines,
composition_start); // Mark that there is an ongoing composition.
is_composing_ = true;
} ::ImmReleaseContext(hwnd_, imc);
}
return ret;
} void OsrImeHandler::DisableIME() {
CleanupComposition();
::ImmAssociateContextEx(hwnd_, NULL, );
} void OsrImeHandler::CancelIME() {
if (is_composing_) {
HIMC imc = ::ImmGetContext(hwnd_);
if (imc) {
::ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, );
::ImmReleaseContext(hwnd_, imc);
}
ResetComposition();
}
} void OsrImeHandler::EnableIME() {
// Load the default IME context.
::ImmAssociateContextEx(hwnd_, NULL, IACE_DEFAULT);
} void OsrImeHandler::UpdateCaretPosition(int index) {
// Save the caret position.
cursor_index_ = index;
// Move the IME window.
MoveImeWindow();
} void OsrImeHandler::ChangeCompositionRange(
const CefRange& selection_range,
const std::vector<CefRect>& bounds) {
composition_range_ = selection_range;
composition_bounds_ = bounds;
MoveImeWindow();
} } // namespace CefSharp

2、OsrImeWin.cpp

#include "Stdafx.h"
#include "OsrImeWin.h"
#include "Internals\CefBrowserHostWrapper.h" namespace CefSharp {
OsrImeWin::OsrImeWin(IntPtr ownerHWnd, IBrowser^ browser) {
_ownerHWnd = ownerHWnd;
_browser = browser;
_wndProcHandler = gcnew WndProcDelegate(this, &OsrImeWin::WndProc); _imeHandler = new OsrImeHandler(static_cast<HWND>(_ownerHWnd.ToPointer()));
} OsrImeWin::~OsrImeWin() {
_wndProcHandler = nullptr;
_browser = nullptr;
if (_imeHandler) {
delete _imeHandler;
}
} void OsrImeWin::OnIMESetContext(UINT message, WPARAM wParam, LPARAM lParam) {
// We handle the IME Composition Window ourselves (but let the IME Candidates
// Window be handled by IME through DefWindowProc()), so clear the
// ISC_SHOWUICOMPOSITIONWINDOW flag:
lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
::DefWindowProc(static_cast<HWND>(_ownerHWnd.ToPointer()), message, wParam, lParam); // Create Caret Window if required
if (_imeHandler) {
_imeHandler->CreateImeWindow();
_imeHandler->MoveImeWindow();
}
} void OsrImeWin::OnIMEStartComposition() {
if (_imeHandler) {
_imeHandler->CreateImeWindow();
_imeHandler->MoveImeWindow();
_imeHandler->ResetComposition();
}
} void OsrImeWin::OnIMEComposition(UINT message, WPARAM wParam, LPARAM lParam) {
if (_browser && _imeHandler) {
CefString cTextStr;
if (_imeHandler->GetResult(lParam, cTextStr)) {
// Send the text to the browser. The |replacement_range| and
// |relative_cursor_pos| params are not used on Windows, so provide
// default invalid values. auto host = safe_cast<CefBrowserHostWrapper^>(_browser->GetHost());
//host->ImeCommitText(cTextStr)
host->ImeCommitText(cTextStr,
CefRange(UINT32_MAX, UINT32_MAX), );
_imeHandler->ResetComposition();
// Continue reading the composition string - Japanese IMEs send both
// GCS_RESULTSTR and GCS_COMPSTR.
} std::vector<CefCompositionUnderline> underlines;
int composition_start = ; if (_imeHandler->GetComposition(lParam, cTextStr, underlines,
composition_start)) {
// Send the composition string to the browser. The |replacement_range|
// param is not used on Windows, so provide a default invalid value. auto host = safe_cast<CefBrowserHostWrapper^>(_browser->GetHost());
host->ImeSetComposition(cTextStr, underlines,
CefRange(UINT32_MAX, UINT32_MAX),
CefRange(composition_start,
static_cast<int>(composition_start + cTextStr.length()))); // Update the Candidate Window position. The cursor is at the end so
// subtract 1. This is safe because IMM32 does not support non-zero-width
// in a composition. Also, negative values are safely ignored in
// MoveImeWindow
_imeHandler->UpdateCaretPosition(composition_start - );
} else {
OnIMECancelCompositionEvent();
}
}
} void OsrImeWin::OnIMECancelCompositionEvent() {
if (_browser && _imeHandler) {
_browser->GetHost()->ImeCancelComposition();
_imeHandler->ResetComposition();
_imeHandler->DestroyImeWindow();
}
} void OsrImeWin::OnImeCompositionRangeChanged(Range selectedRange, array<Rect>^ characterBounds) {
if (_imeHandler) {
// Convert from view coordinates to device coordinates.
/*RectList device_bounds;
CefRenderHandler::RectList::const_iterator it = character_bounds.begin();
for (; it != character_bounds.end(); ++it) {
device_bounds.push_back(LogicalToDevice(*it, device_scale_factor_));
}*/ std::vector<CefRect> device_bounds;
for each(Rect rect in characterBounds) {
CefRect rc(rect.X, rect.Y, rect.Width, rect.Height);
device_bounds.push_back(rc);
} CefRange selection_range(selectedRange.From, selectedRange.To);
_imeHandler->ChangeCompositionRange(selection_range, device_bounds);
}
} void OsrImeWin::OnKeyEvent(int message, int wParam, int lParam) {
if (!_browser)
return; auto host = safe_cast<CefBrowserHostWrapper^>(_browser->GetHost());
host->SendKeyEvent(message, wParam, lParam);
} IntPtr OsrImeWin::WndProc(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam) {
WPARAM pwParam;
switch (message) {
case WM_IME_SETCONTEXT:
OnIMESetContext((UINT)message,
reinterpret_cast<WPARAM>(wParam.ToPointer()),
reinterpret_cast<LPARAM>(lParam.ToPointer()));
return IntPtr::Zero;
case WM_IME_STARTCOMPOSITION:
OnIMEStartComposition();
return IntPtr::Zero;
case WM_IME_COMPOSITION:
OnIMEComposition((UINT)message,
reinterpret_cast<WPARAM>(wParam.ToPointer()),
reinterpret_cast<LPARAM>(lParam.ToPointer()));
return IntPtr::Zero;
case WM_IME_ENDCOMPOSITION:
OnIMECancelCompositionEvent();
// Let WTL call::DefWindowProc() and release its resources.
break;
case WM_SYSCHAR:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP:
case WM_CHAR:
OnKeyEvent(message, reinterpret_cast<int>(wParam.ToPointer()), reinterpret_cast<int>(lParam.ToPointer()));
break;
} return IntPtr();
}
}

3、ChromiumWebBrow 修改请在 github中查看。

解决 CefSharp WPF控件不能使用输入法输入中文的问题(代码已提交到 github)的更多相关文章

  1. Html5下拉控件同时支持文本输入和下拉代码

    有时候,下拉框不能满足我们的业务需求,还需要同时支持用户输入内容,默认的select标签是不支持用户输入的,下面我说一下原生的select如何支持用户输入,代码如下: <!DOCTYPE htm ...

  2. 通过WinForm控件创建的WPF控件无法输入的问题

    今天把写的一个WPF程序发布到别的机器上执行,发现一个比较奇怪的问题:在那个机器上用英文输入法无法输入数字,非要切换到中文输入法才行:但在我的机器上却是好好的. 最开始以为是输入法的问题,弄了好一阵子 ...

  3. Github 开源:使用控制器操作 WinForm/WPF 控件( Sheng.Winform.Controls.Controller)

    利用午休时间继续把过去写的一些代码翻出来说一说,文章可能写的比较简略,但是我会努力把核心意思表达清楚,具体代码可直接访问 Github 获取. Github 地址:https://github.com ...

  4. [转载]WPF控件拖动

    这篇博文总结下WPF中的拖动,文章内容主要包括: 1.拖动窗口 2.拖动控件 Using Visual Studio 2.1thumb控件 2.2Drag.Drop(不连续,没有中间动画) 2.3拖动 ...

  5. WPF 控件库——可拖动选项卡的TabControl

    WPF 控件库系列博文地址: WPF 控件库——仿制Chrome的ColorPicker WPF 控件库——仿制Windows10的进度条 WPF 控件库——轮播控件 WPF 控件库——带有惯性的Sc ...

  6. 反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑) C#中缓存的使用 C#操作redis WPF 控件库——可拖动选项卡的TabControl 【Bootstrap系列】详解Bootstrap-table AutoFac event 和delegate的分别 常见的异步方式async 和 await C# Task用法 c#源码的执行过程

    反爬虫:利用ASP.NET MVC的Filter和缓存(入坑出坑)   背景介绍: 为了平衡社区成员的贡献和索取,一起帮引入了帮帮币.当用户积分(帮帮点)达到一定数额之后,就会“掉落”一定数量的“帮帮 ...

  7. wpf控件开发基础(3) -属性系统(2)

    原文:wpf控件开发基础(3) -属性系统(2) 上篇说明了属性存在的一系列问题. 属性默认值,可以保证属性的有效性. 属性验证有效性,可以对输入的属性进行校验 属性强制回调, 即不管属性有无发生变化 ...

  8. wpf控件开发基础(4) -属性系统(3)

    原文:wpf控件开发基础(4) -属性系统(3) 知识回顾 接上篇,上篇我们真正接触到了依赖属性的用法,以及依赖属性的属性元数据的用法,并且也实实在在地解决了之前第二篇提到的一系列问题.来回顾一下 属 ...

  9. WPF控件深拷贝:序列化/反序列化

    原文:WPF控件深拷贝:序列化/反序列化 今天DebugLZQ在做WPF拖动总结的时候,遇到了这个问题.baidu了下,貌似没有解决这个问题的权威答案,遂写下这篇博文. 我想做的事情是:拖动一个窗体内 ...

随机推荐

  1. UIPopoverPresentationController使用

    UIPopoverPresentationController是什么? iOS8.0之后引入的一个方便开发者创建带箭头的弹出控制器,类似qq消息页面点击右上角加号弹出的视图. UIPopoverPre ...

  2. 360安全检测出的WordPress漏洞的修复方法

    1.跨站脚本攻击(XSS) 这个漏洞注意是因为用户评论可以提交代码,有安全风险.虽然你的WordPress以及是最新版,但是你的WordPress主题却不一定跟着更新!因此,需要稍微修改一下评论相关的 ...

  3. 映射语句之INSERT语句

    1.一个 INSERT SQL 语句可以在<insert>元素在映射器 XML 配置文件中配置 例子: <insert id="insertStudentWithId&qu ...

  4. php使用openssl进行数字签名验证

    <?php /** * Created by PhpStorm. * User: hanks * Date: 6/2/2017 * Time: 6:03 PM */ /* [数字签名] 使用完全 ...

  5. Swift字符串可变性

    您可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量来保证其不会被修改: var variableString = "Horse" variableStrin ...

  6. 关于CSS3中transform变换的小坑

    2017年6月30日15:05:46 今天在写一个demo的时候,发现CSS3中transform变换的一个特性. 首先,我先描述一下我发现的情况(问题再现): <div class=" ...

  7. 短信发送接口被恶意访问的网络攻击事件(四)完结篇--搭建WAF清理战场

    前言 短信发送接口被恶意访问的网络攻击事件(一)紧张的遭遇战险胜 短信发送接口被恶意访问的网络攻击事件(二)肉搏战-阻止恶意请求 短信发送接口被恶意访问的网络攻击事件(三)定位恶意IP的日志分析脚本 ...

  8. SharePoint 2016 安装配置流程及需要注意的地方

    1. 安装域, 安装后创建一个用户用于之后的安装配置, 例如 SPAdmin@XXXXX.com 2. 安装sql server 2016 将要安装sql server 的服务器加入域,   并将域账 ...

  9. [Android FrameWork 6.0源码学习] LayoutInflater 类分析

    LayoutInflater是用来解析XML布局文件,然后生成对象的ViewTree的工具类.是这个工具类的存在,才能让我们写起Layout来那么省劲. 我们接下来进去刨析,看看里边的奥秘 //调用i ...

  10. javaweb利用filter拦截请求

    项目上有个小需求,要限制访问者的IP,屏蔽未授权的登录请求.该场景使用过滤器来做再合适不过了. SecurityFilter.java: package com.lichmama.webdemo.fi ...