0

I'm very new to this, especially WinUI, so please bear with me.

I'm writing an UI app with Visual Studio which communicates with a console program through a JSON file with properties. The UI has two main sections and a setting page. The pages share parts of the properties which will have to be written to the JSON file. I think the NavigationView is perfect for my purpose. In any case, I want to avoid having everything in one window.

This is a new version of a Windows Forms app, which had very few controls and only a main window with menu elements.

  1. I tried to add a similar way by reading properties/variables from other pages.
  • Using static objects works but is a lot of manual work to hook up with the controls.
  • Similarly, such a solution requires a lot of manual work as well.
  • Using two instances doesn't work as each instance seems to update separately.
  1. Since static objects work, I tried to bind controls to static properties.
  • I couldn't find a proper guide for WinUI, so I tried to use something like this.
  • Again, making instances in any form (xaml, code-behind) and update that, never seems to update the source property.
  • I also tried to use non-static properties and make an instance of it on other pages, which to my biggest surprise didn't work either (Maybe I didn't do this correctly?).
  1. There are a number of Stackoverflow questions about this, most of them didn't work out for me (some details in things I tried).
  • This MAUI answer recommends DependencyService, but it seems to be exclusive to Xamarin (and MAUI seems to be different as well).
  • A number of solutions turned out to be exclusive for other app styles (forms, framework, etc). I wasn't able to translate any of these methods to my WinUI app. (Example)
  1. A popular way to use properties seems to be a MVVM.
  • This works on the same page only. It looks like the instances do not update the source either.
  • The previously mentioned solution with static objects didn't work either.
  • I also tried the toolkit MVVM package, trying to copy what the Template Studio for WinUI does, but it was no different.
  • There seems to be ways to share data between ViewModels and even pages (by using their class as models) with a Main ViewModel (link). I don't know how to do that and if it works.
  1. The Template Studio seems to be saving and reading from a file. However, I am concerned about performance since that would constantly read from and write to disk.
  • The only implemented setting in the Template Studio (on only one page), the theme changer writes to yet another file which I must avoid. (I will have a UI settings file but it must be editable with a text editor.)
  • Writing settings to disk would probably mean that I have to add a command for each control, so I can just as well use a method from 1.
  • There seems to be a way to bind settings files (various formats like INI, XML, JSON), which I haven't checked out more closely, as I am concerned about performance.
  1. I saw some other suggestions, such as:
  • DynamicMethod, which seems to have poor performance and doesn't seem to solve my issue.
  • Dependency properties which seem to have poor performance as well (seems to be better now). I couldn't get this to work, probably because I couldn't find a good tutorial. It seems to be a promising solution, so if this is the recommended way, I can provide my non-working code.

Examples:

1. (static properties require a lot of manual assignment)

Page1 (xaml)

                <ToggleSwitch x:Name="test" Header="test switch" OffContent="Off" OnContent="On" Toggled="test_Toggled"/>

Page1 code-behind (C#)

[...]
        public static MyClass Test { get; set; }
[...]
        private void test_Toggled(object sender, RoutedEventArgs e)
        {
            Test.TestSwitch = test.IsOn;
        }
[...]

Page2 code-behind (C#)

using static Namespace.Page1;
[...]
            TextBox.Text = Test.TestSwitch.ToString();
[...]

2. (couldn't figure out proper static binding and using non-static properties doesn't work for me)

Page1 (xaml)

                <ToggleSwitch x:Name="test" Header="test switch" IsOn="{x:Bind Test.TestSwitch, Mode=TwoWay}" OffContent="Off" OnContent="On"/>
                <TextBox x:Name="test2" Text="{x:Bind Test.TestSwitch, Mode=OneWay}"/>  <!--works-->

Page1 code-behind (C#)

[...]
        public MyClass Test { get; }
[...]

Page2 code-behind (C#)

[...]
            Page1 P1 = new();
            TextBox.Text = P1.Test.TestSwitch.ToString();
[...]

4. Find more examples including MVVM in this test app.


This is a general question about how to do this the right way. Did anyone share data in this context before?

*** Please excuse my amateur language.

ak2yny
  • 3
  • 2
  • Gotta say, a pleasant surprise to see such a detailed question from a new contributor to stackoverflow. Well done. – Rand Random Jun 27 '23 at 09:31
  • As per my understanding, it's more easy than you thought. 1st create a global static class with all your properties (that are existing to your json file). Add methods to save and load the json file into the class. Then, to all your UI components you will accessing the exposed properties from the class. In case that user changes anything, then you will call the save method. – Isidoros Moulas Jun 27 '23 at 09:54
  • The question of sharing data between pages (or whatever that can be called in different frameworks like activities, etc.) is not specific to WinUI3. Dependency Injection is one solution that Microsoft loves a lot these days, especially with .NET where many cross-platform libraries are provided. So it's probably a good idea to use it with WinUI3. And no, it's not specific to MAUI (actually MAUI for Windows *is* WinUI3), even works in servers, ASP.NET, etc. Using static data everywhere doesn't super fit with WinUI databinding principal (MVVM or not). – Simon Mourier Jun 27 '23 at 11:24
  • Thanks for the comments. @ Isidoros Moulas : seems like it. I just did it wrong. @ Simon Mourier : I got the impression about static data, I just couldn't figure out the right way with non-static data. Same with Dependency Injection (assuming this DependencyService? sorry for the noob questions), which seems to be less popular and more complicated for beginners like me. Would be better if I had a teacher, but I guess I have to stick around StackOverflow until that happens. – ak2yny Jun 27 '23 at 13:15

1 Answers1

0

IMHO your MVVM approach seems wrong, at least if I fully understood your requirements.

In your provided demo application you have 2 classes TestVM and TestVM2, but you have 3 independent working properties of TestSwitch.

  • TestVM has a member TestSwitch, so each instance of TestVM will have its own state
  • TestVM2 has a member TestSwitch, so each instance of TestVM2 will have its own state
  • TestVM2 has a static instance, with its own state

I would suggest offloading all "shared" members into a seperate class eg. TestVMShared and use a static instance of this class.

So instead of this (code is from the github repository)

public class TestVM : INotifyPropertyChanged
{
    private bool testSwitch;

    public bool TestSwitch
    {
        get => testSwitch;
        set
        {
            if (testSwitch != value)
            {
                testSwitch = value;
                OnPropertyChanged(nameof(TestSwitch));
            }
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
public class TestVM2 : INotifyPropertyChanged
{
    //{Binding Source={x:Static local:TestVM.Instance}, Path=TestSwitch}
    private bool testSwitch;
    
    public TestVM2() {}

    public bool TestSwitch
    {
        get => testSwitch;
        set
        {
            if (testSwitch != value)
            {
                testSwitch = value;
                OnPropertyChanged(nameof(TestSwitch));
            }
        }
    }

    private static readonly TestVM2? _instance;

    public static TestVM2 Instance => _instance ?? new TestVM2();

    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

I would do this:

// note: 
// I added this class, but it is irrelevant to the answer it just reduces code
// you should consider looking into using a library offering MVVM goodies 
// eg. https://github.com/CommunityToolkit/WindowsCommunityToolkit

public class VMBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class TestVM : VMBase
{
    public TestVMShared Shared { get; } = TestVMShared.Instance;
}
public class TestVM2 : VMBase
{
    public TestVMShared Shared { get; } = TestVMShared.Instance;
}

public class TestVMShared : VMBase
{
    public static TestVMShared Instance { get; } = new();

    private bool testSwitch;

    public bool TestSwitch
    {
        get => testSwitch;
        set
        {
            if (testSwitch != value)
            {
                testSwitch = value;
                OnPropertyChanged(nameof(TestSwitch));
            }
        }
    }
}

With this every instance either of TestVM or TestVM2 will always use the same instance of TestVMShared, as I am using the static instance.

Edit:

Didn't quite understand, why you only specified an DesignInstance in Tab_One.xaml, so I did the following additions

enter image description here

Rand Random
  • 7,300
  • 10
  • 40
  • 88
  • 1
    Thanks, this works like a charm. I thought it was complicated, but it turns out that I just didn't do this right. It seems like there are several good approaches, but the MVVM way appeals to me, so I think I'll stick with it. Sorry for the confusion with the design instance and two view models. – ak2yny Jun 27 '23 at 13:05