c# - ItemsControl do not update itself when ObservableCollection fires CollectionChanged -
i've read lot of answers of question, contains "you missed inotifypropertychanged". use mvvm light implementation of viewmodelbase, observableobject etc.
view:
<window x:class="baseflyingfigure.mainwindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:baseflyingfigure" xmlns:helpers="clr-namespace:baseflyingfigure.helpers" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:cmd="clr-namespace:galasoft.mvvmlight.command;assembly=galasoft.mvvmlight.platform" xmlns:system="clr-namespace:system;assembly=mscorlib" mc:ignorable="d" title="mainwindow" height="450" width="525" datacontext="{binding mainviewmodel, source={staticresource locator}}"> <i:interaction.triggers> <i:eventtrigger eventname="loaded"> <cmd:eventtocommand command="{binding loadedcommand}" /> </i:eventtrigger> <i:eventtrigger eventname="previewkeydown"> <cmd:eventtocommand command="{binding previewkeydowncommand}" passeventargstocommand="true" /> </i:eventtrigger> </i:interaction.triggers> <grid> <grid.rowdefinitions> <rowdefinition height="auto" /> <rowdefinition height="*" /> </grid.rowdefinitions> <menu grid.row="0"> <menuitem header="file"> <menuitem header="exit" command="{binding appexitcommand}" /> </menuitem> </menu> <itemscontrol grid.row="1" helpers:sizeobserver.observe="true" helpers:sizeobserver.observedwidth="{binding canvaswidth, mode=onewaytosource}" helpers:sizeobserver.observedheight="{binding canvasheight, mode=onewaytosource}" itemssource="{binding elements, converter={helpers:elementtoshapeconverter}, mode=oneway, updatesourcetrigger=propertychanged}" > <itemscontrol.itemspanel> <itemspaneltemplate> <canvas cliptobounds="true" /> </itemspaneltemplate> </itemscontrol.itemspanel> </itemscontrol> </grid>
viewmodel:
using system.collections.objectmodel; using system.diagnostics; using system.windows; using system.windows.input; using system.windows.media; using system.windows.shapes; using baseflyingfigure.services.interfaces; using galasoft.mvvmlight; using galasoft.mvvmlight.command; namespace baseflyingfigure.viewmodels { public class mainviewmodel : viewmodelbase { private readonly ifigurerepository _repository; private observablecollection<element> _elements = new observablecollection<element>(); public mainviewmodel(ifigurerepository repository) { _repository = repository; appexitcommand = new relaycommand(exit); loadedcommand = new relaycommand(windowloaded); previewkeydowncommand = new relaycommand<keyeventargs>(previewkeydown); elements.add(new element(new ellipse { fill = brushes.hotpink, width = 100, height = 100 }) {left = 250, top = 250}); } public relaycommand appexitcommand { get; private set; } public relaycommand loadedcommand { get; private set; } public relaycommand<keyeventargs> previewkeydowncommand { get; private set; } public double canvaswidth { get; set; } public double canvasheight { get; set; } public observablecollection<element> elements { { return _elements; } set { if (value != _elements) set(ref _elements, value); } } private void previewkeydown(keyeventargs e) { switch (e.key) { case key.oemplus: elements.add(new element(new ellipse { fill = brushes.hotpink, width = 100, height = 100 }) {left = 250, top = 250}); debug.writeline("+"); break; } } private void windowloaded() { elements.collectionchanged += (sender, args) => debug.writeline("changed"); } private void exit() => application.current.shutdown(); } }
converter:
using system; using system.collections.generic; using system.globalization; using system.linq; using system.windows.data; using system.windows.markup; using baseflyingfigure.viewmodels; namespace baseflyingfigure.helpers { public class elementtoshapeconverter : markupextension, ivalueconverter { private static elementtoshapeconverter _converter; public object convert(object value, type targettype, object parameter, cultureinfo culture) { var list = (value icollection<element>)?.select(el => el.shape).tolist(); return list; } public object convertback(object value, type targettype, object parameter, cultureinfo culture) { return null; } public override object providevalue(iserviceprovider serviceprovider) { return _converter ?? (_converter = new elementtoshapeconverter()); } } }
element:
using system.windows.controls; using system.windows.shapes; using galasoft.mvvmlight; namespace baseflyingfigure.viewmodels { public class element : observableobject { private double _left; private shape _shape; private double _top; public element(shape shape) { shape = shape; } public double left { { return _left; } set { set(ref _left, value); canvas.setleft(shape, value); } } public double top { { return _top; } set { set(ref _top, value); canvas.settop(shape, value); } } public shape shape { { return _shape; } set { set(ref _shape, value); } } } }
collectionchanged fires when press +. canvas display shapes made , added in constructor. viewmodellocator:
public class viewmodellocator { public viewmodellocator() { servicelocator.setlocatorprovider(() => simpleioc.default); simpleioc.default.register<mainviewmodel>(); simpleioc.default.register<ifigurerepository, figurerepository>(); } public mainviewmodel mainviewmodel => servicelocator.current.getinstance<mainviewmodel>(); }
what's wrong that? mistakes in xaml or else?
the basic problem of approach use shape
objects in view model.
besides impossible have more 1 view visualizes view model (because uielements can have single parent), forces use unconventional , defective approach convert observablecollection<element>
list<shape>
in converter of itemssource binding. stated in comments , other answer, list<shape>
returned converter not notify view changes in observablecollection<element>
.
a proper mvvm approach use representation of shape without ui elements, e.g. this:
public class element { public geometry shape { get; set; } public brush fill { get; set; } public brush stroke { get; set; } public double strokethickness { get; set; } }
you declare regular datatemplate visualization of shape in itemscontrol:
<itemscontrol itemssource="{binding elements}"> <itemscontrol.itemspanel> <itemspaneltemplate> <canvas/> </itemspaneltemplate> </itemscontrol.itemspanel> <itemscontrol.itemtemplate> <datatemplate> <path data="{binding shape}" fill="{binding fill}" stroke="{binding stroke}" strokethickness="{binding strokethickness}"/> </datatemplate> </itemscontrol.itemtemplate> </itemscontrol>
adding sample ellipse view model this:
elements.add(new element { shape = new ellipsegeometry(new point(250, 250), 50, 50), fill = brushes.hotpink });
if reason need additional x/y position offset each element, may add 2 properties element class
public class element { ... public double x { get; set; } public double y { get; set; } }
and add itemscontainerstyle
itemscontrol uses these properties:
<itemscontrol itemssource="{binding elements}"> ... <itemscontrol.itemcontainerstyle> <style targettype="contentpresenter"> <setter property="canvas.left" value="{binding x}"/> <setter property="canvas.top" value="{binding y}"/> </style> </itemscontrol.itemcontainerstyle> </itemscontrol>
Comments
Post a Comment