Skip to content

Commit 7c34ea4

Browse files
authored
Custom tree view looses focussed checkbox on toggle (microsoft#186906)
Fixes microsoft#186306
1 parent 7326ef7 commit 7c34ea4

File tree

1 file changed

+81
-71
lines changed

1 file changed

+81
-71
lines changed

src/vs/workbench/browser/parts/views/treeView.ts

Lines changed: 81 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -630,79 +630,16 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
630630
this._register(focusTracker.onDidBlur(() => this.focused = false));
631631
}
632632

633-
private updateCheckboxes(items: ITreeItem[]) {
634-
const additionalItems: ITreeItem[] = [];
635-
636-
if (!this.manuallyManageCheckboxes) {
637-
for (const item of items) {
638-
if (item.checkbox !== undefined) {
639-
640-
function checkChildren(currentItem: ITreeItem) {
641-
for (const child of (currentItem.children ?? [])) {
642-
if ((child.checkbox !== undefined) && (currentItem.checkbox !== undefined) && (child.checkbox.isChecked !== currentItem.checkbox.isChecked)) {
643-
child.checkbox.isChecked = currentItem.checkbox.isChecked;
644-
additionalItems.push(child);
645-
checkChildren(child);
646-
}
647-
}
648-
}
649-
checkChildren(item);
650-
651-
const visitedParents: Set<ITreeItem> = new Set();
652-
function checkParents(currentItem: ITreeItem) {
653-
if (currentItem.parent && (currentItem.parent.checkbox !== undefined) && currentItem.parent.children) {
654-
if (visitedParents.has(currentItem.parent)) {
655-
return;
656-
} else {
657-
visitedParents.add(currentItem.parent);
658-
}
659-
660-
let someUnchecked = false;
661-
let someChecked = false;
662-
for (const child of currentItem.parent.children) {
663-
if (someUnchecked && someChecked) {
664-
break;
665-
}
666-
if (child.checkbox !== undefined) {
667-
if (child.checkbox.isChecked) {
668-
someChecked = true;
669-
} else {
670-
someUnchecked = true;
671-
}
672-
}
673-
}
674-
if (someChecked && !someUnchecked && (currentItem.parent.checkbox.isChecked !== true)) {
675-
currentItem.parent.checkbox.isChecked = true;
676-
additionalItems.push(currentItem.parent);
677-
checkParents(currentItem.parent);
678-
} else if (someUnchecked && (currentItem.parent.checkbox.isChecked !== false)) {
679-
currentItem.parent.checkbox.isChecked = false;
680-
additionalItems.push(currentItem.parent);
681-
checkParents(currentItem.parent);
682-
}
683-
}
684-
}
685-
checkParents(item);
686-
}
687-
}
688-
}
689-
items = items.concat(additionalItems);
690-
items.forEach(item => this.tree?.rerender(item));
691-
this._onDidChangeCheckboxState.fire(items);
692-
}
693-
694-
695633
protected createTree() {
696634
const actionViewItemProvider = createActionViewItem.bind(undefined, this.instantiationService);
697635
const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id));
698636
this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
699637
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.id }, () => task));
700638
const aligner = new Aligner(this.themeService);
701639
const checkboxStateHandler = this._register(new CheckboxStateHandler());
702-
this._register(checkboxStateHandler.onDidChangeCheckboxState(items => {
703-
this.updateCheckboxes(items);
704-
}));
705-
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler);
640+
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner, checkboxStateHandler, this.manuallyManageCheckboxes);
641+
this._register(renderer.onDidChangeCheckboxState(e => this._onDidChangeCheckboxState.fire(e)));
642+
706643
const widgetAriaLabel = this._title;
707644

708645
this.tree = this._register(this.instantiationService.createInstance(Tree, this.id, this.treeContainer!, new TreeViewDelegate(), [renderer],
@@ -1125,10 +1062,13 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
11251062
static readonly ITEM_HEIGHT = 22;
11261063
static readonly TREE_TEMPLATE_ID = 'treeExplorer';
11271064

1065+
private readonly _onDidChangeCheckboxState: Emitter<readonly ITreeItem[]> = this._register(new Emitter<readonly ITreeItem[]>());
1066+
readonly onDidChangeCheckboxState: Event<readonly ITreeItem[]> = this._onDidChangeCheckboxState.event;
1067+
11281068
private _actionRunner: MultipleSelectionActionRunner | undefined;
11291069
private _hoverDelegate: IHoverDelegate;
11301070
private _hasCheckbox: boolean = false;
1131-
private _renderedElements = new Map<ITreeNode<ITreeItem, FuzzyScore>, ITreeExplorerTemplateData>();
1071+
private _renderedElements = new Map<string, { original: ITreeNode<ITreeItem, FuzzyScore>; rendered: ITreeExplorerTemplateData }>(); // tree item handle to template data
11321072

11331073
constructor(
11341074
private treeViewId: string,
@@ -1137,6 +1077,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
11371077
private actionViewItemProvider: IActionViewItemProvider,
11381078
private aligner: Aligner,
11391079
private checkboxStateHandler: CheckboxStateHandler,
1080+
private readonly manuallyManageCheckboxes: boolean,
11401081
@IThemeService private readonly themeService: IThemeService,
11411082
@IConfigurationService private readonly configurationService: IConfigurationService,
11421083
@ILabelService private readonly labelService: ILabelService,
@@ -1151,6 +1092,9 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
11511092
};
11521093
this._register(this.themeService.onDidFileIconThemeChange(() => this.rerender()));
11531094
this._register(this.themeService.onDidColorThemeChange(() => this.rerender()));
1095+
this._register(checkboxStateHandler.onDidChangeCheckboxState(items => {
1096+
this.updateCheckboxes(items);
1097+
}));
11541098
}
11551099

11561100
get templateId(): string {
@@ -1302,7 +1246,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
13021246
this.treeViewsService.addRenderedTreeItemElement(node, templateData.container);
13031247

13041248
// remember rendered element
1305-
this._renderedElements.set(element, templateData);
1249+
this._renderedElements.set(element.element.handle, { original: element, rendered: templateData });
13061250
}
13071251

13081252
private rerender() {
@@ -1312,8 +1256,8 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
13121256
for (const key of keys) {
13131257
const value = this._renderedElements.get(key);
13141258
if (value) {
1315-
this.disposeElement(key, 0, value);
1316-
this.renderElement(key, 0, value);
1259+
this.disposeElement(value.original, 0, value.rendered);
1260+
this.renderElement(value.original, 0, value.rendered);
13171261
}
13181262
}
13191263
}
@@ -1381,10 +1325,76 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
13811325
return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE;
13821326
}
13831327

1328+
private updateCheckboxes(items: ITreeItem[]) {
1329+
const additionalItems: ITreeItem[] = [];
1330+
1331+
if (!this.manuallyManageCheckboxes) {
1332+
for (const item of items) {
1333+
if (item.checkbox !== undefined) {
1334+
1335+
function checkChildren(currentItem: ITreeItem) {
1336+
for (const child of (currentItem.children ?? [])) {
1337+
if ((child.checkbox !== undefined) && (currentItem.checkbox !== undefined) && (child.checkbox.isChecked !== currentItem.checkbox.isChecked)) {
1338+
child.checkbox.isChecked = currentItem.checkbox.isChecked;
1339+
additionalItems.push(child);
1340+
checkChildren(child);
1341+
}
1342+
}
1343+
}
1344+
checkChildren(item);
1345+
1346+
const visitedParents: Set<ITreeItem> = new Set();
1347+
function checkParents(currentItem: ITreeItem) {
1348+
if (currentItem.parent && (currentItem.parent.checkbox !== undefined) && currentItem.parent.children) {
1349+
if (visitedParents.has(currentItem.parent)) {
1350+
return;
1351+
} else {
1352+
visitedParents.add(currentItem.parent);
1353+
}
1354+
1355+
let someUnchecked = false;
1356+
let someChecked = false;
1357+
for (const child of currentItem.parent.children) {
1358+
if (someUnchecked && someChecked) {
1359+
break;
1360+
}
1361+
if (child.checkbox !== undefined) {
1362+
if (child.checkbox.isChecked) {
1363+
someChecked = true;
1364+
} else {
1365+
someUnchecked = true;
1366+
}
1367+
}
1368+
}
1369+
if (someChecked && !someUnchecked && (currentItem.parent.checkbox.isChecked !== true)) {
1370+
currentItem.parent.checkbox.isChecked = true;
1371+
additionalItems.push(currentItem.parent);
1372+
checkParents(currentItem.parent);
1373+
} else if (someUnchecked && (currentItem.parent.checkbox.isChecked !== false)) {
1374+
currentItem.parent.checkbox.isChecked = false;
1375+
additionalItems.push(currentItem.parent);
1376+
checkParents(currentItem.parent);
1377+
}
1378+
}
1379+
}
1380+
checkParents(item);
1381+
}
1382+
}
1383+
}
1384+
items = items.concat(additionalItems);
1385+
items.forEach(item => {
1386+
const renderedItem = this._renderedElements.get(item.handle);
1387+
if (renderedItem) {
1388+
renderedItem.rendered.checkbox?.render(item);
1389+
}
1390+
});
1391+
this._onDidChangeCheckboxState.fire(items);
1392+
}
1393+
13841394
disposeElement(resource: ITreeNode<ITreeItem, FuzzyScore>, index: number, templateData: ITreeExplorerTemplateData): void {
13851395
templateData.elementDisposable.clear();
13861396

1387-
this._renderedElements.delete(resource);
1397+
this._renderedElements.delete(resource.element.handle);
13881398
this.treeViewsService.removeRenderedTreeItemElement(resource.element);
13891399

13901400
templateData.checkbox?.dispose();

0 commit comments

Comments
 (0)