1

TL;DR - I have successfully created the components which can read a JSON file from a URL and present it to my application. I want to be able to change some of the values in the JSON using XAML WPF and then save the changed items to a local JSON file.

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Newtonsoft.Json;
using System.Net.Http;

namespace NewApp
{
   /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            appxConfigsUri.Text = "https://raw.githubusercontent.com/msft-jasonparker/WPF-Test/main/appconfig.json";
        }

        async void btnLoadAppxJson_Click(object sender, RoutedEventArgs e)
        {
            string Uri = appxConfigsUri.Text;
            var httpClient = new HttpClient();
            var resultJson = await httpClient.GetStringAsync(Uri);

            var resultPackages = JsonConvert.DeserializeObject<AppConfigs[]>(resultJson);

            appconfigs.ItemsSource = resultPackages;
        }

        private void btnSaveJsonToFile_Click(object sender, RoutedEventArgs e)
        {

        }
    }
}

MainWindow.xaml

<Window x:Class="NewApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:NewApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" MaxWidth="800">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <TabControl x:Name="tabControl">
        <TabItem Header="App Config">
            <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
                <Grid HorizontalAlignment="Stretch" VerticalAlignment="Top">
                    <Label Content="Source:" Margin="5,5,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Bold" FontSize="13.333" Width="100" VerticalContentAlignment="Center" HorizontalContentAlignment="Right" Padding="5,1,5,1"/>
                    <TextBox x:Name="appxConfigsUri" Text="appconfig.json" Margin="110,5,5,0" TextWrapping="WrapWithOverflow" FontSize="13.333" TextAlignment="Justify" Height="38" VerticalAlignment="Top" Padding="5,0,0,0" MinWidth="450" IsReadOnly="True" BorderBrush="{x:Null}" IsTabStop="False" BorderThickness="0,0,0,0"/>
                    <Button x:Name="btnLoadAppxJson" Content="Load JSON" Width="100" Height="28" Margin="5,48,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Normal" FontSize="14.667" Click="btnLoadAppxJson_Click"/>
                    <Button x:Name="btnSaveJsonToFile" Content="Save JSON" Width="100" Height="28" Margin="122,48,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" FontWeight="Normal" FontSize="14.667" Click="btnSaveJsonToFile_Click"/>
                    <Separator HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="5,85,5,0"/>
                    <StackPanel Margin="5,90,5,5" Background="#FFDADADA" ScrollViewer.CanContentScroll="True">
                        <ListView x:Name="appconfigs" Background="#FFC5C5C5" BorderBrush="{x:Null}" HorizontalContentAlignment="Stretch" HorizontalAlignment="Left" Margin="5,5,5,5" ScrollViewer.CanContentScroll="True">
                            <ListView.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel ScrollViewer.CanContentScroll="True">
                                        <Grid x:Name="ItemLayout" HorizontalAlignment="Left" Background="#FF858585" Margin="5,5,5,5" ScrollViewer.CanContentScroll="True">
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="160"/>
                                                <ColumnDefinition Width="494"/>
                                            </Grid.ColumnDefinitions>
                                            <Grid.RowDefinitions>
                                                <RowDefinition MinHeight="28"/>
                                                <RowDefinition MinHeight="28"/>
                                                <RowDefinition MinHeight="28"/>
                                                <RowDefinition MinHeight="28" MaxHeight="90"/>
                                            </Grid.RowDefinitions>
                                            <Label Content="Package Name:" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Center" HorizontalContentAlignment="Right" VerticalContentAlignment="Center" Margin="5,0,5,0" FontWeight="Bold" Padding="1,1,1,1"/>
                                            <TextBlock Text="{Binding ItemName}" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Center" Padding="5,0,5,0" ScrollViewer.VerticalScrollBarVisibility="Auto" TextWrapping="WrapWithOverflow" Margin="5,0,5,0"/>
                                    <Label Content="Recommended Setting:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" HorizontalContentAlignment="Right" VerticalContentAlignment="Center" Margin="5,0,5,0" FontWeight="Bold" Padding="1,1,1,1"/>
                                    <TextBlock Text="{Binding Setting}" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" Padding="5,0,5,0" ScrollViewer.VerticalScrollBarVisibility="Auto" TextWrapping="WrapWithOverflow" Margin="5,0,5,0"/>

                                    <Label Content="New Setting:" Grid.Column="0" Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Center" HorizontalContentAlignment="Right" VerticalContentAlignment="Center" Margin="5,0,5,0" FontWeight="Bold" Padding="1,1,1,1"/>
                                    <ComboBox Grid.Column="1" Grid.Row="2" Margin="5,0,5,0" VerticalAlignment="Center" HorizontalAlignment="Left" Text="Not Configured" >
                                                <ComboBoxItem Content="Enabled"/>
                                                <ComboBoxItem Content="Disabled"/>
                                            </ComboBox>
                                            <Label Content="Description:" Grid.Column="0" Grid.Row="3" HorizontalAlignment="Stretch" VerticalAlignment="Top" HorizontalContentAlignment="Right" VerticalContentAlignment="Center" Margin="5,5,5,5" FontWeight="Bold" Padding="1,1,1,1"/>
                                            <TextBox VerticalScrollBarVisibility="Auto" Text="{Binding Description}" Grid.Column="1" Grid.Row="3" Padding="5,2,5,2" TextWrapping="WrapWithOverflow" Margin="5,5,5,5" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsReadOnly="True" Height="80" MinWidth="450"/>
                                        </Grid>
                                    </StackPanel>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                        </ListView>
                    </StackPanel>
                </Grid>
            </ScrollViewer>
        </TabItem>
        <TabItem Header="Future Config">
            <Grid Background="#FFE5E5E5"/>
        </TabItem>
    </TabControl>
</Window>

configurations.cs

using System;
using Newtonsoft.Json;

namespace NewApp
{
    public class AppConfigs
    {
        [JsonProperty("ItemName")]
        public string ItemName { get; set; }

        [JsonProperty("Setting")]
        public string Setting { get; set; }

        [JsonProperty("URL")]
        public Uri Url { get; set; }

        [JsonProperty("Description")]
        public string Description { get; set; }
    }

}

Ideally, the app will have multiple tabs for various configurations. The user would adjust the configurations in the UI and then save the new settings as JSON to the local system.

XAML and C# is not my primary skill, but learning as I go. Any help would be appreciated!

J0R3D1N
  • 11
  • 1
  • 2
  • In the simplest case, store the `resultPackages` collection not in a local variable, but in a private field. Use DataGrid for editing instead of ListView. And, after making changes, save the `resultPackages` collection. – EldHasp May 29 '21 at 16:00
  • The only thing to be edited is the ComboBox with a dropdown. I want the dropdown to be the *new* value which is saved to the local JSON. I'll look into the private field though. – J0R3D1N May 29 '21 at 16:50
  • Which property of the `AppConfigs` class should the ComboBox change? – EldHasp May 29 '21 at 18:15
  • I’d like the application to capture the user changing the setting from its default to some value from the combo box. – J0R3D1N May 31 '21 at 01:18
  • You display three AppConfigs properties on three lines. On another line, you have an unbound ComboBox. To preserve its state, it must also be tied to something. You can remove the line with the Setting output and replace the Label there with a ComboBox. You can leave the lines as they are, but add a duplicate property in the AppConfigs to which to bind the ComboBox. And then rewrite from this property to Setting. But then you won't be able to use JSON models in the View. – EldHasp May 31 '21 at 05:23
  • To give a more accurate answer to your question, I need you to add more details to it. `Setting` is of type string. ComboBox has only two meanings. What if the `Setting` value is missing in the ComboBox? Or is this an incredible situation? Why do you need two separate lines with the current `Setting` state and the value the user wants to set? Is there a provision for canceling user-entered states? At what point or on what event do you need to save the states entered by the user? – EldHasp May 31 '21 at 05:32
  • The label and textbox for the recommended setting isn't needed or necessary. I only included it to provide the user with what the incoming JSON file was defaulted to. In the end the workflow would be the following: 1. User clicks the Load JSON button 2. JSON is loaded into ListView 3. User reviews each item and selects an item from the dropdown 4. After the user has made all selections, they click Save JSON 5. The code should use the value from the comboBox and replace the value in the original setting property. – J0R3D1N Jun 02 '21 at 19:25
  • We clearly misunderstand each other. Below I will give the answer, perhaps due to a misunderstanding, this will not be exactly what you need. Then you add clarifying details in the comments (or in your question) and I will try to take them into account. – EldHasp Jun 03 '21 at 09:52

1 Answers1

0

I give the answer to the best of my understanding of the question and clarifying comments.

You have a collection of AppConfigs instances.
How you get it is essentially irrelevant to the question.
You need to enable the user, using the ComboBox, to change the value of the Setting property to two predefined ones.
The most important caveat is that the ComboBox source must contain a collection of these valid values, not ComboBoxItem.
The second nuance, the selected item in the ComboBox must be bound to the Setting property.

One of the possible ways to implement it:

using System;
using System.Collections.Generic;

namespace AppConfigsEdit
{
    public class AppConfigs
    {
        public string ItemName { get; set; }

        public string Setting { get; set; }
        public string DefaultSetting { get; set; } = SettingValues[0];

        public Uri Url { get; set; }

        public string Description { get; set; }

        /// <summary>The collection of valid values
        /// for the property <see cref="Setting"/>.</summary>
        public static IReadOnlyList<string> SettingValues { get; }
            = new List<string>() { "Enabled", "Disabled" }
            .AsReadOnly();
    }
}
using Simplified;
using System;
using System.Collections.ObjectModel;

namespace AppConfigsEdit
{
    public class AceViewModel : BaseInpc
    {
        private RelayCommand _loadConfigsCommand;
        private RelayCommand _saveConfigsCommand;
        private string _configsSource;

        public ObservableCollection<AppConfigs> Configs { get; }
            = new ObservableCollection<AppConfigs>();

        public string ConfigsSource { get => _configsSource; set => Set(ref _configsSource, value); }

        public RelayCommand LoadConfigsCommand => _loadConfigsCommand
            ?? (_loadConfigsCommand = new RelayCommand(LoadConfigsExecute));
        private void LoadConfigsExecute(object parameter)
        {
            // Here's the code that loads the JSON
            // and populates the Settings collection.
            // Example:
            string Uri = ConfigsSource;
            var httpClient = new HttpClient();
            var resultJson = await httpClient.GetStringAsync(Uri);

            Configs.Clear();
            foreach (var config in JsonConvert.DeserializeObject<AppConfigs[]>(resultJson))
            {
                Configs.Add(config);
            }
        }

        public RelayCommand SaveConfigsCommand => _saveConfigsCommand
            ?? (_saveConfigsCommand = new RelayCommand(SaveConfigsExecute));

        private void SaveConfigsExecute(object parameter)
        {
            // Here is the code that saves the Settings collection in JSON
        }

        public AceViewModel()
        {
            // Temporarily for debugging:
            Configs.Add(new AppConfigs() { Url = new Uri("file.json", UriKind.Relative), ItemName = "First", Description = "blah, blah, blah", Setting = AppConfigs.SettingValues[0] });
            Configs.Add(new AppConfigs() { Url = new Uri("file.json", UriKind.Relative), ItemName = "Second", Description = "blah, blah, blah", Setting = AppConfigs.SettingValues[1] });
        }
    }
}
<Window x:Class="AppConfigsEdit.AceWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AppConfigsEdit"
        mc:Ignorable="d"
        Title="AceWindow" Height="600" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <FrameworkElement.DataContext>
            <local:AceViewModel ConfigsSource="appconfig.json"/>
        </FrameworkElement.DataContext>
        <StackPanel Background="AliceBlue">
            <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="Source:" Margin="5" HorizontalAlignment="Right" FontWeight="Bold" FontSize="13"/>
                <TextBox Grid.Column="1" Margin="5" Text="{Binding ConfigsSource}" FontSize="13"
                         TextAlignment="Justify" BorderBrush="{x:Null}" BorderThickness="0,0,0,0"/>
            </Grid>
            <StackPanel Orientation="Horizontal">
                <Button Content="Load JSON" Margin="5" Padding="15 5" FontSize="15"
                        Command="{Binding LoadConfigsCommand, Mode=OneWay}"/>
                <Button Content="Save JSON" Margin="5" Padding="15 5" FontSize="15"
                        Command="{Binding SaveConfigsCommand, Mode=OneWay}"/>

            </StackPanel>
        </StackPanel>
        <Separator Grid.Row="1" Margin="5"/>
        <ListView Grid.Row="2" Margin="5" Background="#FFC5C5C5" BorderBrush="{x:Null}"
                  HorizontalContentAlignment="Stretch" ScrollViewer.CanContentScroll="True"
                  ItemsSource="{Binding Configs}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <StackPanel ScrollViewer.CanContentScroll="True">
                        <Grid x:Name="ItemLayout" HorizontalAlignment="Left" Background="#FF858585" Margin="5,5,5,5" ScrollViewer.CanContentScroll="True">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="160"/>
                                <ColumnDefinition Width="494"/>
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition MinHeight="28"/>
                                <RowDefinition MinHeight="28"/>
                                <RowDefinition MinHeight="28"/>
                                <RowDefinition MinHeight="28" MaxHeight="90"/>
                            </Grid.RowDefinitions>
                            <TextBlock Text="Package Name:" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Center" TextAlignment="Right" Margin="5,0,5,0" FontWeight="Bold" Padding="1"/>
                            <TextBlock Text="{Binding ItemName}" Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Center" Padding="1" Margin="5,0,5,0"/>
                            <TextBlock Text="Recommended Setting:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" TextAlignment="Right" Margin="5,0,5,0" FontWeight="Bold" Padding="1"/>
                            <TextBlock Text="{Binding DefaultSetting}" Grid.Column="1" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Center" Padding="5,0,5,0"  Margin="5,0,5,0"/>
                            <TextBlock Text="New Setting:" Grid.Column="0" Grid.Row="2" HorizontalAlignment="Stretch" VerticalAlignment="Center" TextAlignment="Right" Margin="5,0,5,0" FontWeight="Bold" Padding="1"/>
                            <ComboBox Grid.Column="1" Grid.Row="2" Margin="5,0,5,0" VerticalAlignment="Center" HorizontalAlignment="Left"
                                      ItemsSource="{x:Static local:AppConfigs.SettingValues}"
                                      SelectedItem="{Binding Setting, UpdateSourceTrigger=PropertyChanged}"/>
                            <TextBlock Text="Description:" Grid.Column="0" Grid.Row="3" HorizontalAlignment="Stretch" VerticalAlignment="Top" TextAlignment="Right" Margin="5" FontWeight="Bold" Padding="1"/>
                            <TextBox VerticalScrollBarVisibility="Auto" Text="{Binding Description}" Grid.Column="1" Grid.Row="3" Padding="5,2,5,2" TextWrapping="WrapWithOverflow" Margin="5,5,5,5" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" IsReadOnly="True" Height="80" MinWidth="450"/>
                        </Grid>
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Window>

The example uses the BaseInpc and RelayCommand classes.

EldHasp
  • 6,079
  • 2
  • 9
  • 24