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

Popular posts from this blog

python - How to insert QWidgets in the middle of a Layout? -

python - serve multiple gunicorn django instances under nginx ubuntu -

module - Prestashop displayPaymentReturn hook url -