WinUI(C#):如何在页面之间共享数据 – 正确的方式?

huangapple go评论75阅读模式
英文:

WinUI (C#): How To Share Data Between Pages - The Proper Way?

问题

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

我对这方面非常新手,尤其是WinUI,请谅解。

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.

我正在使用Visual Studio编写一个UI应用程序,它通过一个带有属性的JSON文件与控制台程序通信。UI有两个主要部分和一个设置页面。这些页面共享属性的一部分,这些属性将需要写入JSON文件。

I think the NavigationView is perfect for my purpose. In any case, I want to avoid having everything in one window.

我认为NavigationView非常适合我的目的。无论如何,我想避免将所有内容都放在一个窗口中。

  1. I tried to add a similar way by reading properties/variables from other pages.

  2. 我尝试通过从其他页面读取属性/变量的类似方式来添加。

  • 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.

  2. 既然静态对象可以工作,我尝试将控件绑定静态属性。

  • I couldn't find a proper guide for WinUI, so I tried to use something like this.

  • 我找不到WinUI的合适指南,所以我尝试使用类似这样的方法。

  • Again, making instances in any form (xaml, code-behind) and update that, never seems to update the source property.

  • 再次尝试在任何形式(xaml,代码后台)中创建实例并进行更新,似乎永远不会更新源属性。

  • 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).

  2. 有许多关于这个问题的Stackoverflow问题,其中大多数对我来说都没有用(在我尝试的一些细节中)。

  • This MAUI answer recommends DependencyService, but it seems to be exclusive to Xamarin (and MAUI seems to be different as well).

  • 这个MAUI的回答建议使用DependencyService,但它似乎只适用于Xamarin(而且MAUI似乎也不同)。

  • 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)

  • 发现许多解决方案只适用于其他应用程序样式(forms,framework等)。我无法将这些方法中的任何一种翻译成我的WinUI应用程序中。(示例)

  1. A popular way to use properties seems to be a MVVM.

  2. 一种常见的使用属性的方式似乎是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.

  • 我还尝试了工具包MVVM包,试图复制WinUI的Template Studio所做的事情,但没有什么不同。

  • 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.

  • 似乎有一些方法可以在ViewModels甚至页面之间共享数据(通过使用它们的类作为模型),并使用Main ViewModel(链接)。我不知道如何做以及它是否有效。

  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.

  2. 模板工作室似乎在文件中保存和读取。但是,我担心性能,因为这将不断地从磁盘读取和写入。

  • 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.)

  • 模板工作室中唯一

英文:

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.

答案1

得分: 0

In my humble opinion, your MVVM approach appears to be incorrect, at least if I fully comprehend your requirements.

在我看来,你的MVVM方法似乎不正确,至少如果我完全理解你的需求的话。

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

在你提供的演示应用程序中,你有2个类TestVMTestVM2,但是你有3个独立的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.

  • TestVM有一个成员TestSwitch,所以每个TestVM的实例都有自己的状态。

  • TestVM2有一个成员TestSwitch,所以每个TestVM2的实例都有自己的状态。

  • TestVM2有一个静态实例,拥有自己的状态。

I would suggest offloading all "shared" members into a separate class, e.g., TestVMShared, and use a static instance of this class.

我建议将所有“共享”成员分离到一个单独的类中,例如TestVMShared,并使用这个类的静态实例。

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

因此,不要使用这种方式(代码来自GitHub存储库):

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:

我会这样做:

// 注意:
// 我添加了这个类,但它与答案无关,只是减少了代码
// 你应该考虑使用一个提供MVVM功能的库
// 例如 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 approach, every instance of either TestVM or TestVM2 will always use the same instance of TestVMShared, as I am using the static instance.

采用这种方法,TestVMTestVM2的每个实例都将始终使用TestVMShared的相同实例,因为我使用了静态实例。

Edit:

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

编辑:

不太理解为什么你只在Tab_One.xaml中指定了一个DesignInstance,所以我做了以下添加:

WinUI(C#):如何在页面之间共享数据 – 正确的方式?

英文:

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

WinUI(C#):如何在页面之间共享数据 – 正确的方式?

huangapple
  • 本文由 发表于 2023年6月27日 17:16:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76563365.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定