Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

How do I separate one screen into separate views #54

Answered by ChebanovDD
MadScientist11 asked this question in Q&A
Discussion options

Hi!
First of all, this is an awesome library and I really like it! I didn't use MVVM before, so It's kind of hard for me to grasp everything for now, so maybe my question is stupid, but let's say I have a screen with several panels, I thought about separating them into different views to maybe reuse some of them in other screens. I don't understand how to do that, since my panels are all in one UI Document.
Also maybe I'm thinking in the wrong way, I would be glad If you could correct me.

You must be logged in to vote

You can use the BindingContextProvider.

Progression

public class ProgressionViewModel : IBindingContext
{
 [Observable] private readonly IProperty<int> _task = new Property<int>(69);
}
<ui:UXML xmlns:uitk="UnityMvvmToolkit.UITK.BindableUIElements" ...>
 <uitk:BindingContextProvider name="BindingContextProvider">
 <uitk:BindableLabel binding-text-path="Task" />
 </uitk:BindingContextProvider>
</ui:UXML>

PlayerProgress

public class PlayerProgressViewModel : IBindingContext, IDisposable
{
 [Observable] private readonly IProperty<string> _age;
 [Observable] private readonly IProperty<string> _days;
 private readonly IPlayerLifeCycleService _playerLifeCycleService;
 

Replies: 1 comment 3 replies

Comment options

Hello.
It will be great if you provide more details or add a code example. Also, you can look at templates.

You must be logged in to vote
3 replies
Comment options

After rereading my question, I realized, that I wasn't specific enough, I'm sorry.
I already figured out a way, but not sure if it's the right one, so I would be glad If you could correct me.
I have a UI Document Main Screen, that has several panels(templates) like PlayerProgress, Progression.
Here is my BaseView, which I took from the ToDoList example

public class BaseView<TBindingContext> : DocumentView<TBindingContext>
 where TBindingContext : class, IBindingContext
 {
 private TBindingContext _viewModel;
 private IValueConverter[] _valueConverters;
 [Inject]
 public void Construct(TBindingContext viewModel, IEnumerable<IValueConverter> valueConverters)
 {
 _viewModel = viewModel;
 _valueConverters = valueConverters.ToArray();
 }
 protected override TBindingContext GetBindingContext()
 {
 return _viewModel;
 }
 protected override IValueConverter[] GetValueConverters()
 {
 return _valueConverters;
 }
 protected override IReadOnlyDictionary<Type, object> GetCollectionItemTemplates()
 {
 return null;
 }
 }

I tried to create PlayerProgress and Progression Views and inherit them from my BaseView and create a ViewModel for each of them.
Like so:

public class PlayerProgressView : BaseView<PlayerProgressViewModel>
{
 
}
public class PlayerProgressViewModel : IBindingContext, ITickable
{
 public IProperty<string> Age { get; }
 public IProperty<string> Days { get; }
 private ITimeService _timeService;
 private IPlayerLifeCycleService _playerLifeCycleService;
 public PlayerProgressViewModel(IPlayerLifeCycleService playerLifeCycleService)
 {
 _playerLifeCycleService = playerLifeCycleService;
 Age = new Property<string>($"Age: {playerLifeCycleService.Age}");
 Days = new Property<string>($"Days: {playerLifeCycleService.Days}");
 }
 public void Tick()
 {
 Age.Value = $"Age: {(int)_playerLifeCycleService.Age}";
 Days.Value = $"Days: {(int)_playerLifeCycleService.Days % 365}";
 
 }
}
public class ProgressionView : BaseView<ProgressionViewModel>
{
 
}
public class ProgressionViewModel : IBindingContext
{
 public IProperty<int> Task{ get; }
 ...
}

My problem was that, since I have all UI in one UI Document(MainScreen), I couldn't just add two views on monobehaviour with UI Doc component and expect that I will get all my properties binded from two views.

The way I solved it

public class MainScreenViewModel : IBindingContext
 {
 [Observable("Age")]
 private IReadOnlyProperty<string> _age => _playerProgressViewModel.Age;
 
 [Observable("Days")]
 private IReadOnlyProperty<string> _days => _playerProgressViewModel.Days;
 
 [Observable("Test")]
 private IReadOnlyProperty<int> _test => _progressionPanelsViewModel.Test;
 private PlayerProgressViewModel _playerProgressViewModel;
 private ProgressionPanelsViewModel _progressionPanelsViewModel;
 public MainScreenViewModel(PlayerProgressViewModel playerProgressViewModel,
 ProgressionPanelsViewModel progressionPanelsViewModel)
 {
 _progressionPanelsViewModel = progressionPanelsViewModel;
 _playerProgressViewModel = playerProgressViewModel;
 }
 
 
 }

And now I only need to attach MainScreenView

 public class MainScreenView : BaseView<MainScreenViewModel>
{
}

Is that the right way to solve it?

Comment options

You can use the BindingContextProvider.

Progression

public class ProgressionViewModel : IBindingContext
{
 [Observable] private readonly IProperty<int> _task = new Property<int>(69);
}
<ui:UXML xmlns:uitk="UnityMvvmToolkit.UITK.BindableUIElements" ...>
 <uitk:BindingContextProvider name="BindingContextProvider">
 <uitk:BindableLabel binding-text-path="Task" />
 </uitk:BindingContextProvider>
</ui:UXML>

PlayerProgress

public class PlayerProgressViewModel : IBindingContext, IDisposable
{
 [Observable] private readonly IProperty<string> _age;
 [Observable] private readonly IProperty<string> _days;
 private readonly IPlayerLifeCycleService _playerLifeCycleService;
 public PlayerProgressViewModel(IPlayerLifeCycleService playerLifeCycleService)
 {
 _playerLifeCycleService = playerLifeCycleService;
 _playerLifeCycleService.AgeChanged += OnAgeChanged;
 _playerLifeCycleService.DaysChanged += OnDaysChanged;
 _age = new Property<string>(GetAgeString(playerLifeCycleService.Age));
 _days = new Property<string>(GetDaysString(playerLifeCycleService.Days));
 }
 public void Dispose()
 {
 _playerLifeCycleService.AgeChanged -= OnAgeChanged;
 _playerLifeCycleService.DaysChanged -= OnDaysChanged;
 }
 private void OnAgeChanged(object sender, int age)
 {
 _age.Value = GetAgeString(age);
 }
 private void OnDaysChanged(object sender, int days)
 {
 _days.Value = GetDaysString(days);
 }
 private static string GetAgeString(int age)
 {
 return $"Age: {age}";
 }
 private static string GetDaysString(int days)
 {
 return $"Days: {days}";
 }
}
<ui:UXML ui:UXML xmlns:uitk="UnityMvvmToolkit.UITK.BindableUIElements" ...>
 <uitk:BindingContextProvider name="BindingContextProvider">
 <uitk:BindableLabel binding-text-path="Age" />
 <uitk:BindableLabel binding-text-path="Days" />
 </uitk:BindingContextProvider>
</ui:UXML>

MainScreen

public class MainScreenViewModel : IBindingContext
{
 [Observable] private readonly IReadOnlyProperty<ProgressionViewModel> _progression;
 [Observable] private readonly IReadOnlyProperty<PlayerProgressViewModel> _playerProgress;
 public MainScreenViewModel(PlayerProgressViewModel playerProgressViewModel,
 ProgressionViewModel progressionViewModel)
 {
 _progression = new ReadOnlyProperty<ProgressionViewModel>(progressionViewModel);
 _playerProgress = new ReadOnlyProperty<PlayerProgressViewModel>(playerProgressViewModel);
 }
}
public class MainScreenView : BaseView<MainScreenViewModel>
{
}
public abstract class BaseView<TBindingContext> : DocumentView<TBindingContext>
 where TBindingContext : class, IBindingContext
{
 private TBindingContext _bindingContext;
 private IValueConverter[] _valueConverters;
 [Inject]
 public void Construct(TBindingContext bindingContext, IValueConverter[] valueConverters)
 {
 _bindingContext = bindingContext;
 // You can inject the valueConverters array to avoid memory allocations instead of "IEnumerable<IValueConverter>".
 _valueConverters = valueConverters;
 }
 protected override TBindingContext GetBindingContext()
 {
 return _bindingContext;
 }
 protected override IValueConverter[] GetValueConverters()
 {
 return _valueConverters;
 }
 // Returning null is bad practice.
 // You don't need to override this method if you are not using CollectionItemTemplates.
 //
 // protected override IReadOnlyDictionary<Type, object> GetCollectionItemTemplates()
 // {
 // return null;
 // }
}
<ui:UXML ...>
 <ui:Template name="ProgressionView" ... />
 <ui:Instance template="ProgressionView" name="ProgressionView">
 <AttributeOverrides element-name="BindingContextProvider" binding-context-path="Progression" />
 </ui:Instance>
 <ui:Template name="PlayerProgressView" ... />
 <ui:Instance template="PlayerProgressView" name="PlayerProgressView">
 <AttributeOverrides element-name="BindingContextProvider" binding-context-path="PlayerProgress" />
 </ui:Instance>
</ui:UXML>

PlayerLifeCycleService

public interface IPlayerLifeCycleService
{
 int Age { get; }
 int Days { get; }
 event EventHandler<int> AgeChanged;
 event EventHandler<int> DaysChanged;
}
public class PlayerLifeCycleService : IPlayerLifeCycleService, ITickable
{
 private const float DelaySec = 1.0f;
 private int _age;
 private int _days;
 private float _elapsed;
 public int Age
 {
 get => _age;
 private set
 {
 _age = value;
 AgeChanged?.Invoke(this, value);
 }
 }
 public int Days
 {
 get => _days;
 private set
 {
 _days = value;
 DaysChanged?.Invoke(this, value);
 }
 }
 public event EventHandler<int> AgeChanged;
 public event EventHandler<int> DaysChanged;
 public void Tick()
 {
 _elapsed += Time.deltaTime;
 if (_elapsed < DelaySec)
 {
 return;
 }
 _elapsed = 0.0f;
 Age++;
 Days++;
 }
}

AppContext

public class AppContext : MonoInstaller
{
 public override void InstallBindings()
 {
 InstallValueConverters();
 Container.Bind<MainScreenViewModel>().AsSingle();
 Container.BindInterfacesAndSelfTo<ProgressionViewModel>().AsSingle();
 Container.BindInterfacesAndSelfTo<PlayerProgressViewModel>().AsSingle();
 Container.BindInterfacesTo<PlayerLifeCycleService>().AsSingle();
 }
 private void InstallValueConverters()
 {
 Container
 .Bind<IValueConverter[]>()
 .FromInstance(new IValueConverter[]
 {
 new IntToStrConverter()
 })
 .AsSingle();
 }
}

SeparateViewsUnityPackage.zip

Answer selected by MadScientist11
Comment options

Ohh, now I understand, thank you, works like a charm :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /