-
-
Notifications
You must be signed in to change notification settings - Fork 30
How do I separate one screen into separate views #54
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
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
-
Hello.
It will be great if you provide more details or add a code example. Also, you can look at templates.
Beta Was this translation helpful? Give feedback.
All reactions
-
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?
Beta Was this translation helpful? Give feedback.
All reactions
-
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(); } }
Beta Was this translation helpful? Give feedback.
All reactions
-
Ohh, now I understand, thank you, works like a charm :)
Beta Was this translation helpful? Give feedback.