Add invalidation mechanism

This commit is contained in:
fanlusky committed 2026-06-14 15:14:03 +08:00
1 parent 6bb9ca27d8
commit df881f81bf
8 files changed
+162 -60

No files matched your search

+3 -1
View File
@@ -40,6 +40,7 @@
- 修复了 `TabControl` / `TreeView` / `Accordion` 这类会改变内容结构的控件在切换后未及时重新布局而导致的稳定性问题
- 修复了单行 `TextBox`(如 demo 中的 Search Box)输入光标和文本区域未垂直居中的问题
- 修复了单行 `TextBox` 在垂直居中后输入法候选框未同步跟随文本区域下移的问题
- 新增一版场景级 invalidation 机制:控件现可通过 `InvalidateMeasure` / `InvalidateArrange` / `InvalidateVisual` 统一声明布局或重绘失效,`Scene` / `Window` 会在下一次绘制前自动补做布局,减少到处手动 `Relayout()` 的耦合
## 构建
@@ -105,6 +106,7 @@ build/bin/Debug/msimeui-demo.exe
## 后续工作
- 继续把场景级 invalidation 细化为局部布局和局部重绘,减少全树重排成本
- 继续完善 `TextBox` 的输入、样式和布局能力
- 增加滚动容器、列表、树、菜单和弹出层
- 增加菜单、弹出层、overlay 和更完整的命中/焦点管理
- 引入资源字典、主题和声明式界面描述
+3
View File
@@ -66,6 +66,9 @@ class Visual
virtual bool OnChar(wchar_t ch, LPARAM lParam);
virtual bool OnTimer(UINT_PTR timerId);
virtual HCURSOR GetCursor() const;
void InvalidateMeasure();
void InvalidateArrange();
void InvalidateVisual();
void SetMargin(float uniform);
void SetMargin(Thickness margin);
+9
View File
@@ -11,7 +11,11 @@ class Scene
public:
void SetRoot(std::shared_ptr<Visual> root);
void Attach(Window *window);
void EnsureLayout(const SizeF &size);
void Layout(const SizeF &size);
void InvalidateMeasure();
void InvalidateArrange();
void InvalidateVisual();
void Render(DeviceResources &deviceResources);
Visual *FindVisualAt(const PointF &point);
Visual *FindFocusableAt(const PointF &point);
@@ -19,5 +23,10 @@ class Scene
private:
std::shared_ptr<Visual> root_;
Window *window_ = nullptr;
SizeF lastLayoutSize_ = {};
bool hasLastLayoutSize_ = false;
bool measureDirty_ = true;
bool arrangeDirty_ = true;
};
} // namespace msimeui
+2
View File
@@ -25,6 +25,8 @@ class Window
SizeF ClientPixelsToDips(const SIZE &size) const;
RECT DipsToClientPixels(const RectF &rect) const;
void Invalidate();
void InvalidateMeasure();
void InvalidateArrange();
void Relayout();
void SetScene(std::unique_ptr<Scene> scene);
+15 -53
View File
@@ -323,10 +323,7 @@ void CheckBox::SetChecked(bool checked)
}
checked_ = checked;
if (window_)
{
window_->Invalidate();
}
InvalidateVisual();
}
SizeF CheckBox::Measure(const SizeF &availableSize)
@@ -436,10 +433,7 @@ ProgressBar::ProgressBar(float height) : preferredHeight_(height)
void ProgressBar::SetValue(float value)
{
value_ = std::clamp(value, 0.0f, 1.0f);
if (window_)
{
window_->Invalidate();
}
InvalidateVisual();
}
float ProgressBar::GetValue() const
@@ -608,10 +602,7 @@ void Slider::SetValueInternal(float value, bool notify)
{
const float oldValue = value_;
value_ = std::clamp(value, minValue_, maxValue_);
if (window_)
{
window_->Invalidate();
}
InvalidateVisual();
if (notify && oldValue != value_ && onChanged_)
{
@@ -668,10 +659,7 @@ ListView::ListView(float itemHeight) : itemHeight_(itemHeight)
void ListView::AddItem(Item item)
{
items_.push_back(std::move(item));
if (window_)
{
window_->Invalidate();
}
InvalidateMeasure();
}
void ListView::ClearItems()
@@ -679,10 +667,7 @@ void ListView::ClearItems()
items_.clear();
selectedIndex_ = 0;
pressedIndex_ = static_cast<size_t>(-1);
if (window_)
{
window_->Invalidate();
}
InvalidateMeasure();
}
void ListView::SetOnSelectionChanged(SelectionChangedHandler handler)
@@ -861,10 +846,7 @@ void TreeView::AddRoot(Node node)
{
selectedNode_ = &roots_.front();
}
if (window_)
{
window_->Invalidate();
}
InvalidateMeasure();
}
void TreeView::Clear()
@@ -873,10 +855,7 @@ void TreeView::Clear()
visibleNodes_.clear();
pressedNode_ = nullptr;
selectedNode_ = nullptr;
if (window_)
{
window_->Invalidate();
}
InvalidateMeasure();
}
void TreeView::SetOnSelectionChanged(SelectionChangedHandler handler)
@@ -1017,10 +996,7 @@ bool TreeView::OnMouseUp(const POINT &point, WPARAM keyState)
{
hit->node->expanded = !hit->node->expanded;
BuildVisibleNodes();
if (window_)
{
window_->Relayout();
}
InvalidateMeasure();
}
else
{
@@ -1111,10 +1087,7 @@ void TabControl::AddTab(std::wstring title, std::shared_ptr<Visual> content)
{
tabs_.back().content->Attach(window_);
}
if (window_)
{
window_->Invalidate();
}
InvalidateMeasure();
}
void TabControl::ClearTabs()
@@ -1124,10 +1097,7 @@ void TabControl::ClearTabs()
selectedIndex_ = 0;
pressedIndex_ = static_cast<size_t>(-1);
pressed_ = false;
if (window_)
{
window_->Invalidate();
}
InvalidateMeasure();
}
void TabControl::SetSelectedIndex(size_t index)
@@ -1149,10 +1119,7 @@ void TabControl::SetSelectedIndex(size_t index)
{
onSelectionChanged_(selectedIndex_);
}
if (window_)
{
window_->Relayout();
}
InvalidateMeasure();
}
size_t TabControl::GetSelectedIndex() const
@@ -1370,10 +1337,7 @@ void Accordion::AddSection(std::wstring title, std::shared_ptr<Visual> content,
{
sections_.back().content->Attach(window_);
}
if (window_)
{
window_->Invalidate();
}
InvalidateMeasure();
}
void Accordion::ClearSections()
@@ -1383,10 +1347,7 @@ void Accordion::ClearSections()
contentRects_.clear();
pressed_ = false;
pressedIndex_ = static_cast<size_t>(-1);
if (window_)
{
window_->Invalidate();
}
InvalidateMeasure();
}
void Accordion::SetAllowMultipleExpanded(bool allowMultipleExpanded)
@@ -1408,6 +1369,7 @@ void Accordion::SetAllowMultipleExpanded(bool allowMultipleExpanded)
CollapseOtherSections(firstExpanded);
}
}
InvalidateMeasure();
}
SizeF Accordion::Measure(const SizeF &availableSize)
@@ -1617,7 +1579,7 @@ bool Accordion::OnMouseUp(const POINT &point, WPARAM keyState)
{
CollapseOtherSections(hit);
}
window_->Relayout();
InvalidateMeasure();
}
return true;
+56 -4
View File
@@ -181,6 +181,30 @@ HCURSOR Visual::GetCursor() const
return LoadCursor(nullptr, IDC_ARROW);
}
void Visual::InvalidateMeasure()
{
if (window_)
{
window_->InvalidateMeasure();
}
}
void Visual::InvalidateArrange()
{
if (window_)
{
window_->InvalidateArrange();
}
}
void Visual::InvalidateVisual()
{
if (window_)
{
window_->Invalidate();
}
}
void Visual::SetMargin(float uniform)
{
SetMargin({uniform, uniform, uniform, uniform});
@@ -189,6 +213,7 @@ void Visual::SetMargin(float uniform)
void Visual::SetMargin(Thickness margin)
{
margin_ = margin;
InvalidateMeasure();
}
const Thickness &Visual::GetMargin() const
@@ -204,6 +229,7 @@ void Visual::SetPadding(float uniform)
void Visual::SetPadding(Thickness padding)
{
padding_ = padding;
InvalidateMeasure();
}
const Thickness &Visual::GetPadding() const
@@ -214,71 +240,85 @@ const Thickness &Visual::GetPadding() const
void Visual::SetWidth(float width)
{
explicitWidth_ = std::max(width, 0.0f);
InvalidateMeasure();
}
void Visual::SetHeight(float height)
{
explicitHeight_ = std::max(height, 0.0f);
InvalidateMeasure();
}
void Visual::SetMinWidth(float width)
{
minWidth_ = std::max(width, 0.0f);
InvalidateMeasure();
}
void Visual::SetMinHeight(float height)
{
minHeight_ = std::max(height, 0.0f);
InvalidateMeasure();
}
void Visual::SetMaxWidth(float width)
{
maxWidth_ = width >= 0.0f ? width : -1.0f;
InvalidateMeasure();
}
void Visual::SetMaxHeight(float height)
{
maxHeight_ = height >= 0.0f ? height : -1.0f;
InvalidateMeasure();
}
void Visual::ClearWidth()
{
explicitWidth_ = -1.0f;
InvalidateMeasure();
}
void Visual::ClearHeight()
{
explicitHeight_ = -1.0f;
InvalidateMeasure();
}
void Visual::ClearMinWidth()
{
minWidth_ = 0.0f;
InvalidateMeasure();
}
void Visual::ClearMinHeight()
{
minHeight_ = 0.0f;
InvalidateMeasure();
}
void Visual::ClearMaxWidth()
{
maxWidth_ = -1.0f;
InvalidateMeasure();
}
void Visual::ClearMaxHeight()
{
maxHeight_ = -1.0f;
InvalidateMeasure();
}
void Visual::SetHorizontalAlignment(HorizontalAlignment alignment)
{
horizontalAlignment_ = alignment;
InvalidateArrange();
}
void Visual::SetVerticalAlignment(VerticalAlignment alignment)
{
verticalAlignment_ = alignment;
InvalidateArrange();
}
SizeF Visual::MeasureInLayout(const SizeF &availableSize)
@@ -378,7 +418,12 @@ bool Visual::HasExplicitHeight() const
void Panel::AddChild(std::shared_ptr<Visual> child)
{
if (child && window_)
{
child->Attach(window_);
}
children_.push_back(std::move(child));
InvalidateMeasure();
}
void Panel::Attach(Window *window)
@@ -907,26 +952,35 @@ void ScrollViewer::UpdateContentLayout()
void Grid::AddRow(GridLength length)
{
rowDefinitions_.push_back(length);
InvalidateMeasure();
}
void Grid::AddColumn(GridLength length)
{
columnDefinitions_.push_back(length);
InvalidateMeasure();
}
void Grid::AddChild(std::shared_ptr<Visual> child, size_t row, size_t column, size_t rowSpan, size_t columnSpan)
{
if (child && window_)
{
child->Attach(window_);
}
children_.push_back({std::move(child), {row, column, std::max<size_t>(rowSpan, 1), std::max<size_t>(columnSpan, 1)}, {}});
InvalidateMeasure();
}
void Grid::SetRowSpacing(float spacing)
{
rowSpacing_ = std::max(spacing, 0.0f);
InvalidateMeasure();
}
void Grid::SetColumnSpacing(float spacing)
{
columnSpacing_ = std::max(spacing, 0.0f);
InvalidateMeasure();
}
void Grid::Attach(Window *window)
@@ -1152,6 +1206,7 @@ void Container::SetChild(std::shared_ptr<Visual> child)
{
child_->Attach(window_);
}
InvalidateMeasure();
}
void Container::Attach(Window *window)
@@ -1350,10 +1405,7 @@ TextBlock::TextBlock(std::wstring text, float fontSize, D2D1_COLOR_F color, bool
void TextBlock::SetText(std::wstring text)
{
text_ = std::move(text);
if (window_)
{
window_->Invalidate();
}
InvalidateMeasure();
}
SizeF TextBlock::Measure(const SizeF &availableSize)
+45
View File
@@ -15,20 +15,40 @@ constexpr float kScenePaddingDips = 5.0f;
void Scene::SetRoot(std::shared_ptr<Visual> root)
{
root_ = std::move(root);
if (root_ && window_)
{
root_->Attach(window_);
}
measureDirty_ = true;
arrangeDirty_ = true;
}
void Scene::Attach(Window *window)
{
window_ = window;
if (root_)
{
root_->Attach(window);
}
}
void Scene::EnsureLayout(const SizeF &size)
{
const bool sizeChanged = !hasLastLayoutSize_ || lastLayoutSize_.width != size.width || lastLayoutSize_.height != size.height;
if (sizeChanged || measureDirty_ || arrangeDirty_)
{
Layout(size);
}
}
void Scene::Layout(const SizeF &size)
{
if (!root_)
{
lastLayoutSize_ = size;
hasLastLayoutSize_ = true;
measureDirty_ = false;
arrangeDirty_ = false;
return;
}
@@ -36,6 +56,31 @@ void Scene::Layout(const SizeF &size)
std::max(size.height - kScenePaddingDips * 2.0f, 0.0f)};
root_->MeasureInLayout(innerSize);
root_->ArrangeInLayout({kScenePaddingDips, kScenePaddingDips, innerSize.width, innerSize.height});
lastLayoutSize_ = size;
hasLastLayoutSize_ = true;
measureDirty_ = false;
arrangeDirty_ = false;
}
void Scene::InvalidateMeasure()
{
measureDirty_ = true;
arrangeDirty_ = true;
InvalidateVisual();
}
void Scene::InvalidateArrange()
{
arrangeDirty_ = true;
InvalidateVisual();
}
void Scene::InvalidateVisual()
{
if (window_)
{
window_->Invalidate();
}
}
void Scene::Render(DeviceResources &deviceResources)
+29 -2
View File
@@ -107,6 +107,28 @@ void Window::Invalidate()
InvalidateRect(hwnd_, nullptr, FALSE);
}
void Window::InvalidateMeasure()
{
if (scene_)
{
scene_->InvalidateMeasure();
return;
}
Invalidate();
}
void Window::InvalidateArrange()
{
if (scene_)
{
scene_->InvalidateArrange();
return;
}
Invalidate();
}
void Window::Relayout()
{
if (!hwnd_ || !scene_)
@@ -117,7 +139,8 @@ void Window::Relayout()
RECT rc = {};
GetClientRect(hwnd_, &rc);
const SIZE pixelSize = {rc.right, rc.bottom};
scene_->Layout(ClientPixelsToDips(pixelSize));
scene_->InvalidateMeasure();
scene_->EnsureLayout(ClientPixelsToDips(pixelSize));
InvalidateRect(hwnd_, nullptr, FALSE);
}
@@ -306,6 +329,10 @@ void Window::OnPaint()
target->Clear(D2D1::ColorF(0xF3F5F8));
if (scene_)
{
RECT rc = {};
GetClientRect(hwnd_, &rc);
const SIZE pixelSize = {rc.right, rc.bottom};
scene_->EnsureLayout(ClientPixelsToDips(pixelSize));
scene_->Render(deviceResources_);
}
@@ -328,7 +355,7 @@ void Window::OnSize()
if (scene_)
{
const SIZE pixelSize = {rc.right, rc.bottom};
scene_->Layout(ClientPixelsToDips(pixelSize));
scene_->EnsureLayout(ClientPixelsToDips(pixelSize));
}
InvalidateRect(hwnd_, nullptr, FALSE);