英文:
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非常适合我的目的。无论如何,我想避免将所有内容都放在一个窗口中。
-
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.
-
使用两个实例不起作用,因为每个实例似乎会分别更新。
-
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.
-
我找不到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?).
-
我还尝试使用非静态属性,并在其他页面上创建一个实例,令我最惊讶的是这也没有起作用(也许我没有做对?)。
-
There are a number of Stackoverflow questions about this, most of them didn't work out for me (some details in things I tried).
-
有许多关于这个问题的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应用程序中。(示例)
-
A popular way to use properties seems to be a MVVM.
-
一种常见的使用属性的方式似乎是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(链接)。我不知道如何做以及它是否有效。
-
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.)
-
模板工作室中唯一
英文:
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.
- 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.
- 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?).
- 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)
- 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.
- 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.
- 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个类TestVM
和TestVM2
,但是你有3个独立的TestSwitch
属性。
-
TestVM
has a memberTestSwitch
, so each instance ofTestVM
will have its own state. -
TestVM2
has a memberTestSwitch
, so each instance ofTestVM2
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.
采用这种方法,TestVM
或TestVM2
的每个实例都将始终使用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
,所以我做了以下添加:
英文:
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 memberTestSwitch
, so each instance ofTestVM
will have its own stateTestVM2
has a memberTestSwitch
, so each instance ofTestVM2
will have its own stateTestVM2
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论