HelixToolkit ZoomExtentsWhenLoaded и Binding

Я использую HelixToolkit для отображения 3D-моделей в WPF. Загрузка работает нормально, но модель не масштабируется должным образом, хотя я использую ZoomExtentWhenLoaded="True" (я ожидаю, что она будет увеличена, чтобы соответствовать моему окну). Модель предоставляется в ViewModel и добавляется в область просмотра через Binding.

Вот мой код:

Вид

<h:HelixViewport3D ZoomExtentsWhenLoaded="True">
  <h:HelixViewport3D.Camera>
     <PerspectiveCamera/>
  </h:HelixViewport3D.Camera>
  <h:DefaultLights/>
  <ModelVisual3D Content="{Binding CurrentModel}"  />
</h:HelixViewport3D>

и части ViewModel

Model3DGroup _currentModel;
public Model3DGroup CurrentModel
{
  get { return _currentModel; }
  set
  {
    _currentModel = value;
    OnPropertyChanged(nameof(CurrentModel));
  }
}

private void OnModelSelectionChanged(object sender, EventArgs args)
{
...
  if (SelectedModel == null)
    return;

  var model = LoadModelFromFile(SelectedModel.Path);
  CurrentModel = model;
}

private Model3DGroup LoadModelFromFile(string objPath, string texturePath = "")
{
   try
   {
      ObjReader objReader = new ObjReader();
      var model = objReader.Read(objPath);

      ApplyTexture(model, texturePath);
      return model;
   }
   catch (Exception e)
   {
     ...
   }
   return null;
}

private void ApplyTexture(Model3DGroup model, string texture)
{
...
   Material material;
   if (!string.IsNullOrEmpty(texture))
   {
     material = MaterialHelper.CreateImageMaterial(texture);
   }
   else
   {
     material = MaterialHelper.CreateMaterial(Colors.LightBlue);
   }

   foreach (var m in model.Children)
   {
     var mGeo = m as GeometryModel3D;
     mGeo.Material = material;
    }
}
...

Я попытался использовать прикрепленное свойство вместо ZoomExtentsWhenLoaded="True" и запустить ZoomExtents() оттуда, но не могу найти правильное событие, которое фактически запускается при изменении модели. Как я могу заставить ZoomExtentsWhenLoaded работать правильно? Или это все-таки неправильное свойство? Как настроить масштабирование и трансформацию, чтобы модель подошла к моему окну? Спасибо за помощь!


person tabina    schedule 26.02.2020    source источник


Ответы (1)


Свойство ZoomToExtentsWhenLoaded — это свойство ViewPort, которое будет пустым при «Загрузке» в смысле WPF UserControl. Содержимое вашего ViewPort представляет собой ModelVisual3D, который привязан к Model3DGroup, который вы предоставили в своей ViewModel, и не будет обновляться до тех пор, пока элемент управления не будет загружен.

Вы хотите, чтобы ViewPort масштабировался до экстентов Model3DGroup при изменении свойства. Один из способов добиться этого — прослушивать в View событие из ViewModel (см. предложение по адресу https://github.com/helix-toolkit/helix-toolkit/issues/1265) - я предоставил описание этого в следующем абзаце.

ViewModel запускает событие в методе набора свойств, а View отвечает на событие, вызывая viewport.ZoomToExtents(). Вы можете использовать простое событие C#, но поскольку это нарушает несвязанный характер привязки данных в MVVM, некоторые предпочитают искать альтернативный маршрут. Если вы используете инфраструктуру MVVM, часто поддерживается присоединение представлений к моделям представления способами, которые уменьшают связь (например, см. IViewAware для Caliburn Micro).

Альтернативный способ состоит в том, чтобы думать о ViewModel как о предоставлении «области интереса» представлению, к которой представление масштабируется при изменении значения этого свойства. Это привело меня к созданию небольшого прикрепленного свойства, которое можно использовать следующим образом (используя ваш пример)...

<h:HelixViewport3D helixextensions:AutoFit.Bounds="{Binding Path=CurrentModel.Bounds}">
    <h:HelixViewport3D.Camera>
        <PerspectiveCamera />
    </h:HelixViewport3D.Camera>
    <h:DefaultLights/>
    <ModelVisual3D Content="{Binding CurrentModel}"/>
</h:HelixViewport3D>

Вы включаете это в свой файл XAML, используя xmlns:helixextensions="clr-namespace:HelixExtensions" в объявлении Window или UserControl.

Это определение присоединенного свойства...

namespace HelixExtensions
{
    public static class AutoFit
    {
        private static readonly Type OwnerType = typeof(AutoFit);

        public static readonly DependencyProperty BoundsProperty =
            DependencyProperty.RegisterAttached(
                "Bounds",
                typeof(Rect3D),
                OwnerType,
                new PropertyMetadata(Rect3D.Empty, OnDataContextChanged)
            );

        public static bool GetBounds(HelixViewport3D obj)
        {
            return (bool)obj.GetValue(BoundsProperty);
        }

        public static void SetBounds(HelixViewport3D obj, bool value)
        {
            obj.SetValue(BoundsProperty, value);
        }

        private static void OnDataContextChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var viewport = d as HelixViewport3D;
            if (viewport.DataContext == null) return;
            viewport.ZoomExtents((Rect3D)e.NewValue);
        }
    }
}

Он работает, позволяя вам привязываться к Rect3D (ограничивающей 3D-рамке) вашего свойства CurrentModel. При изменении свойства CurrentModel будет вызываться метод OnDataContextChanged, который масштабирует ViewPort до новых экстентов.

Если вы хотите масштабировать экстенты более чем одной модели или у вас есть определенные границы, на которых вы хотите сосредоточиться, вы можете указать это как свойство Rect3D в своей ViewModel и просто привязаться к этому свойству, а не к CurrentModel.Bounds. В простых приложениях этот дополнительный уровень контроля не нужен.

e.g.

<h:HelixViewport3D helixextensions:AutoFit.Bounds="{Binding Path=CurrentFocusBounds}">
    <h:HelixViewport3D.Camera>
        <PerspectiveCamera />
    </h:HelixViewport3D.Camera>
    <h:DefaultLights/>
    <ModelVisual3D Content="{Binding CurrentModel}"/>
</h:HelixViewport3D>
person Matt Breckon    schedule 16.06.2020