+1

MVVM with background work

Anonymous 10 years ago 0
This discussion was imported from CodePlex

g_todeschini wrote at 2013-08-13 17:41:

I'm trying to create a WPF application using the MVVM pattern.
In my view (UserControl class derived) I've placed the HelixViewport3D control and I've assigned an instance of my ViewModel to the DataContext property

<h:HelixViewport3D RotateGesture="Alt+RightClick" PanGesture="RightClick">
<h:HelixViewport3D.Camera>
<OrthographicCamera Position="80.22,0,0" LookDirection="-80.22,0,0" UpDirection="0,0,1" Width="56"/>
</h:HelixViewport3D.Camera>
<h:SunLight />
<ModelVisual3D Content="{Binding Model3D}" />
</h:HelixViewport3D>

The ModelVisual3D content property is binded with the Model3D propery of my ViewModel.

All works fine but I would like to create the model in a function that works in background. I've tried to do this using both the BackgroundWorker class that the Task.Factory.StartNew but I've always get the message that the object is property of another thread.

One of my attempt is the following

public class MyViewModel : BaseViewModel
{
private MyData _model; // the data on which to generate my 3d model
private Model3DGroup _model3d;
private CancellationTokenSource _source;

public Model3DGroup Model3D
{
    get { return _model3d; }
    set
    {
        if (_model3d != value)
        {
            _model3d = value;
             RaisePropertyChanged(() => Model3D);
        }
    }
}

private void CreateModel3D(object dispatcherObject)
{
    Dispatcher dispatcher = (Dispatcher)dispatcherObject;

    Material nodesMaterial = MaterialHelper.CreateMaterial(Colors.Green);
    double size = Math.Max(_model.Limits.Width, _model.Limits.Height);
    double dn = size / 100;
    double db = size / 120;
    int thetaDiv = 10;

    foreach (Strauss.Node node in _model.Nodes)
    {
         MeshBuilder meshBuilder = new MeshBuilder(false, false);
         meshBuilder.AddSphere(new Point3D(node.X, node.Y, node.Z), dn, thetaDiv, thetaDiv / 2);
         MeshGeometry3D mesh = meshBuilder.ToMesh(true);
         dispatcher.Invoke((Action)(() => _model3d.Children.Add(new GeometryModel3D { Geometry = mesh, Material = nodesMaterial, BackMaterial = nodesMaterial })));
    }
}

private void ModelLoaded(object sender, EventArgs args)
{
    System.Windows.Window win = App.Current.Windows[0];
    DocManagerViewModel docManager = win.DataContext as DocManagerViewModel;
    _source = new CancellationTokenSource();
    Task task = Task.Factory.StartNew(CreateModel3D, _model3d.Dispatcher, _source.Token);
}
}

The process starts from the ModelLoaded event handler.

Can anyone please help me?
Thanks in advance.

RobPerkins wrote at 2013-08-14 18:02:

1 -- Use Task.Factory.StartNew(). BackgroundWorker is prone to a race condition.
2 -- All WPF objects, including GeometryModel3D and MeshGeometry3D, have to be "Frozen" to be passed between threads. See "Freezable" in the MSDN documentation.

Change the code in CreateModel3D from what you have to something like:
var gm3d = new GeometryModel3D { Geometry = mesh, Material = nodesMaterial, BackMaterial = nodesMaterial };
dispatcher.Invoke((Action)(() => _model3d.Children.Add(gm3d.GetAsFrozen())));