I am trying to move the following function listView_SelectionChanged away from code-behind and handle it directly inside my ViewModel (or directly as XAML). And I was hoping that someone might have a better idea on how to implement this.
The TextBox contains Sections e.g. [Secion1] and to help navigate I have a ListBox on the side of the TextBox that contains a list of all Sections. If you click on one of the Sections it will automatically jump to that part of the Text.
The code currently looks something like this:
XAML
ListBox ItemsSource="{Binding Path=Sections}" Name="listBox"
SelectionMode="Single" Width="170"
DisplayMemberPath="Section"
SelectionChanged="listView_SelectionChanged"/>
<TextBox Name="TextBox1" Text="{Binding Path=Source}"/>
Model
public class SourceData
{
public SourceData()
{
Sections = new List<SectionData>();
}
public String Source { get; set; }
public List<SectionData> Sections { get; set; }
}
public class SectionData
{
public int Line { get; set; } // Line of the Section
public String Section { get; set; } // Section name (e.g. [Section1]
}
Code-behind
private void listView_SelectionChanged(object sender,
System.Windows.Controls.SelectionChangedEventArgs e)
{
var test = (SectionData)listBox.SelectedItem; // Get Information on the Section
if (test.Line > 0 && test.Line <= TextBox1.LineCount) // Validate
{
TextBox1.ScrollToLine(test.Line - 1); // Scroll to Line
}
}
In such situations I usually create an attached behavior (in your case it will be a behavior which will allow synchronizing textbox scrolled line), add property in ViewModel (SourceData) which will rule that attached behavior and bind behavior to this property.
Steps you should do in your case (I assume you know how to create an attached properties):
1) Create attached behavior ScrolledLine for textbox. It should support at least one-way binding. In attached property callback you will scroll textBox (to which behavior is attached) to the line. Below you will find a quick sample how to implement such a behavior.
2) Your SourceData should be extended with at least two properties: SelectedSection and ScrolledLine. ScrolledLine should be raising PropertyChanged. SelectedSection setter should change ScrolledLine:
private SectionData _selectedSection;
public SectionData SelectedSection
{
get { return _selectedSection; }
set
{
_selectedSection = value;
if (_selectedSection != null) SelectedLine = _selectedSection.Line;
}
}
3) Bind your view to these two new properties:
b below is xml-namespace for your attached behavior from #1
<ListBox ItemsSource="{Binding Path=Sections}" SelectionMode="Single" Width="170" DisplayMemberPath="Section" SelectedItem="{Binding SelectedSection, Mode=TwoWay}" />
<TextBox Text="{Binding Path=Source}" b:Behaviors.ScrolledLine="{Binding ScrolledLine}" />
4) Remove your listView_SelectionChanged event handler from view. Your view should not have any code except InitializeComponent from now on.
P.S.: Here is a sample how your attached behavior should look like:
public class b:Behaviors
{
#region Attached DP registration
public static int GetScrolledLine(TextBox obj)
{
return (int)obj.GetValue(ScrolledLineProperty);
}
public static void SetScrolledLine(TextBox obj, int value)
{
obj.SetValue(ScrolledLineProperty, value);
}
#endregion
public static readonly DependencyProperty ScrolledLineProperty=
DependencyProperty.RegisterAttached("ScrolledLine", typeof(int), typeof(Behaviors), new PropertyMetadata(ScrolledLine_Callback));
// This callback will be invoked when 'ScrolledLine' property will be changed. Here you should scroll a textbox
private static void ScrolledLine_Callback(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
var textbox = (TextBox) source;
int newLineValue = (int)e.NewValue;
if (newLineValue > 0 && newLineValue <= textBox.LineCount) // Validate
textbox.ScrollToLine(newLineValue - 1); // Scroll to Line
}
}
Related
I have a class with a property 'DesignParameters' that upon changing, would affect another property called 'AxialMomentDataLists'. However, the 'DesignParameters' is comprised of a bunch of other 'child' properties that are accessible through a datagrid on the UI and also implement property changed. If one of the child properties changes, I also want 'DesignParameters' to automatically update, which would in-turn call for a new 'AxialMomentDataLists' to be set. Does anyone have advice on the best method to achieve this?
public class Column : ObservableObject
{
private double length;
private DesignParameters desParameters;
public DesignParameters DesParameters
{
get { return desParameters; }
set
{
desParameters = value;
RaisePropertyChanged(nameof(DesParameters));
RaisePropertyChanged(nameof(AxialMomentDataLists));
}
}
public List<AxialMomentDataSet> AxialMomentDataLists
{
get { return CalculateAxialMomentLists(ColumnForce, DesParameters); }
set { }
}
}
Excerpt from child class:
public class DesignParameters : ObservableObject
{
#region Private variables
private double phiC;
private double phiS;
private int cover;
private int reinforcementYieldStrength;
private int concreteStrength;
private double b;
private double h;
private LiveLoadReductionType liveLoadReduction;
private StirrupType stirrupBar;
private int numberOfXBars;
private int numberOfYBars;
private BarDiameterType longitudinalBarDiameter;
private double longitudinalReinforcementPercentage;
List<Bar> rebar;
#endregion
public int NumberOfXBars
{
get { return numberOfXBars; }
set
{
numberOfXBars = PropertyMethods.SetNumberOfBars("x", value, B, H, Cover, LongitudinalBarDiameter);
RaisePropertyChanged(nameof(NumberOfXBars));
RaisePropertyChanged(nameof(Rebar));
RaisePropertyChanged(nameof(LongitudinalReinforcementPercentage));
RaisePropertyChanged(nameof(AxialResistance));
}
}
}
EDIT
I have created a more simple piece of code that tries to accomplish approximately the same thing that I am here (https://github.com/dtnaughton/SampleApp)
Basically, I had a UI that binds to properties on the FamilyMember and the properties Age and Name can be changed. On the Family (parent) class, I have a property CumulativeFamilyAge which returns a method that basically sums up the age of the family member (in this example, there is only 1 family member and therefore it should read the same as the FamilyMember.Age.
When the user changes FamilyMember.Age on the UI, I want Family to detect that one of it's child properties has changed, and therefore update CumulativeFamilyAge accordingly.
For example, if the 'NumberOfXBars' property gets changed (it is exposed on the UI) then I need the parent property 'DesignParameters' on the Column class to recognize one of it's child properties has changed and therefore this must update.
I'll write as I understand it.
In the Column class, you have a DesParameters property that contains an instance of DesignParameters.
If the value of one of the properties (NumberOfXBars) of this instance has changed, then you want to update all views associated with this instance.
Here you have an obvious problem with breaking the contract of the INotifyPropertyChanged interface. According to this contract, the PropertyChanged event does not fire when a value is assigned to a property, but only if this value is different from the previous one.
For this reason, if you just raise this event for the same value, then the bindings will not react to this.
As I think, you are simply not thinking correctly.
You don't need to update the bindings to the instance.
You need to update any bindings to any of its properties.
For this, the interface contract provides for the creation of an event with an empty argument.
If the above is a correct understanding of your problem, then try this implementation:
public int NumberOfXBars
{
get { return numberOfXBars; }
set
{
numberOfXBars = PropertyMethods.SetNumberOfBars("x", value, B, H, Cover, LongitudinalBarDiameter);
RaisePropertyChanged(string.Empty);
}
}
Example implementation after additional question:
I have created a more simple piece of code that tries to accomplish approximately the same thing that I am here (https://github.com/dtnaughton/SampleApp)
Improved implementation of Person entity using generic MVVMLight methods.
using GalaSoft.MvvmLight;
namespace SampleApp.Models
{
public class Person : ObservableObject
{
private string _name;
private int _age;
public string Name { get => _name; set => Set(ref _name, value); }
public int Age { get => _age; set => Set(ref _age, value); }
public Person()
{
Name = "TestName";
Age = 30;
}
}
}
Family is derived from ViewModelBase to be able to watch event properties change.
This is done to demonstrate how to observe changes in entities in properties.
Also added a second Person entity to improve the demo example.
using GalaSoft.MvvmLight;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace SampleApp.Models
{
public class Family : ViewModelBase
{
private Person _familyMember;
private int _cumulativeFamilyAge;
private Person _childMember;
public Person FamilyMember { get => _familyMember; set => Set(ref _familyMember, value); }
public Person ChildMember { get => _childMember; set => Set(ref _childMember, value); }
public int CumulativeFamilyAge { get => _cumulativeFamilyAge; private set => Set(ref _cumulativeFamilyAge, value); }
public Family()
{
FamilyMember = new Person() { Name = "Father" };
ChildMember = new Person() { Name = "Son", Age = 7 };
}
public override void RaisePropertyChanged<T>([CallerMemberName] string propertyName = null, T oldValue = default, T newValue = default, bool broadcast = false)
{
base.RaisePropertyChanged(propertyName, oldValue, newValue, broadcast);
if (propertyName == nameof(FamilyMember) || propertyName == nameof(ChildMember))
{
if (oldValue is Person oldPerson)
oldPerson.PropertyChanged -= OnPersonPropertyChanged;
if (newValue is Person newPerson)
newPerson.PropertyChanged += OnPersonPropertyChanged;
CalculateCumulativeFamilyAge();
}
}
private void OnPersonPropertyChanged(object sender, PropertyChangedEventArgs e)
{
CalculateCumulativeFamilyAge();
}
public void CalculateCumulativeFamilyAge()
{
CumulativeFamilyAge = (FamilyMember?.Age ?? 0)
+ (ChildMember?.Age ?? 0);
return;
}
}
}
The Data Context of the Window is best defined in XAML - this makes XAML design much easier.
If you need to have a field in Code Behind with a link to the ViewModel, you should get it from XAML.
using SampleApp.ViewModels;
using System.Windows;
namespace SampleApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private readonly MainViewModel viewModel /*= new MainViewModel()*/;
public MainWindow()
{
InitializeComponent();
//DataContext = viewModel;
viewModel = (MainViewModel)DataContext;
}
}
}
Added a DataTemplate for the Person entity in the Window and set the output for both entities.
<Window x:Class="SampleApp.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:viewmodels="clr-namespace:SampleApp.ViewModels"
xmlns:local="clr-namespace:SampleApp.Models"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<viewmodels:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type local:Person}">
<Border Margin="5" Background="LightSkyBlue" Padding="5">
<StackPanel>
<TextBox Text="{Binding Name}" Height="30"/>
<TextBox Text="{Binding Age}" Height="30"/>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<ContentPresenter Content="{Binding FamilyModel.FamilyMember}"/>
<ContentPresenter Content="{Binding FamilyModel.ChildMember}"/>
<TextBlock Text="{Binding FamilyModel.CumulativeFamilyAge}" Height="30"/>
</StackPanel>
</Grid>
</Window>
I am not sure if I unterstood you correctly. DesignParameters implements INotifyPropertyChanged and whenever one of its properties changes, you want to invoke PropertyChanged in your Column class for the AxialMomentDataLists property?
If so, this is quite easy. Just subscribe to this event whenever you set a new value to your DesParameters property. Don't forget to unsubscribe the event from the old value. A null check might be necessary (or do you use C# 8 with nullable reference types?)
public class Column : ObservableObject
{
private double length;
private DesignParameters desParameters;
public DesignParameters DesParameters
{
get { return desParameters; }
set
{
if(desParameters != null)
{
desParameters.PropertyChanged -= DesParametersChildPropertyChanged;
}
desParameters = value;
desParameters.PropertyChanged += DesParametersChildPropertyChanged;
RaisePropertyChanged(nameof(DesParameters));
RaisePropertyChanged(nameof(AxialMomentDataLists));
}
}
private void DesParametersChildPropertyChanged(object sender, PropertyChangeEventArgs args)
{
RaisePropertyChanged(nameof(DesParameters));
RaisePropertyChanged(nameof(AxialMomentDataLists));
}
public List<AxialMomentDataSet> AxialMomentDataLists
{
get { return CalculateAxialMomentLists(ColumnForce, DesParameters); }
set { }
}
}
I have a View that have two comboboxes. One is where user selects routing pipe type name, and the other where there should be a list of available diameters for the chosen pipe type.
Whenever user selects the pipe type, the other combobox should update the list of available diameters.
AvailableDiameters and RoutingPipeTypeName properties are static in Context class, that implements INotifyPropertyChanged interface. In xaml I've set the bindings to these properties, in code behind also the DataContext.
The problem is that the list of diameters get's updated only once, when the view is initialized.
When debugging I can see that the properties backing field's values are updated properly when selection on pipe type name is changed, only in the UI the available diameters list is not updated...
Context class:
public class Context : INotifyPropertyChanged
{
public static Context This { get; set; } = new Context();
public static string RoutingPipeTypeName
{
get => _routingPipeTypeName;
set
{
if (_routingPipeTypeName != value)
{
_routingPipeTypeName = value;
This.OnPropertyChanged(nameof(RoutingPipeTypeName));
}
}
}
public static List<double> AvailableDiameters
{
get => _availableDiameters;
set
{
//check if new list's elements are not equal
if (!value.All(_availableDiameters.Contains))
{
_availableDiameters = value;
This.OnPropertyChanged(nameof(AvailableDiameters));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
xaml:
<ComboBox Width="80" SelectedValue="{Binding Path=RoutingPipeTypeName, Mode=OneWayToSource}">
<ComboBoxItem Content="Example pipe type 1"></ComboBoxItem>
<ComboBoxItem Content="Example pipe type 2"></ComboBoxItem>
</ComboBox>
<ComboBox Width="80" SelectedValue="{Binding Path=RoutingDiameter, Mode=OneWayToSource}" ItemsSource="{Binding Path=AvailableDiameters, Mode=OneWay}">
</ComboBox>
code behind:
public Context AppContext => Context.This;
public MyView()
{
InitializeComponent();
Instance = this;
DataContext = AppContext;
}
And the client class that is responsible for updating the list of diameters:
public void InitializeUIContext()
{
Context.This.PropertyChanged += UIContextChanged;
if (Cache.CachedPipeTypes.Count > 0)
Context.RoutingPipeTypeName = Cache.CachedPipeTypes.First().Key;
}
private void UIContextChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Context.RoutingPipeTypeName))
{
Context.AvailableDiameters = Cache.CachedPipeTypes.First().Value.GetAvailableDiameters();
}
}
I expected such set-up would update the diameters combobox each time the selection is changed on the pipe types property.
Instead it updates it only once, when the view is initialized... Why?
Do not use static properties for binding to an object (which you have correctly passed to the DataContext of your view).
Declare the properties without the static modifier and replace This.OnPropertyChanged by OnPropertyChanged:
public string RoutingPipeTypeName
{
get => _routingPipeTypeName;
set
{
if (_routingPipeTypeName != value)
{
_routingPipeTypeName = value;
OnPropertyChanged(nameof(RoutingPipeTypeName));
}
}
}
You should also remove the static This from your Context class and simply write
public Context AppContext { get; } = new Context();
My simple program has two windows:
from the first one I set a Boolean value, which then...
I'll use in the second window to disable a number of TextBoxes depending on the aforementioned value itself.
Said TextBoxes are also characterized by a validation binding. At now my validation task is flawlessly working, but I'm not able to make the binding to the IsEnabled TextBox property work.
This is the snippet of my XAML containing one of the TextBoxes (for now the only one I've bound):
<TextBox x:Name="tbSlave1" Validation.Error="ValidationError" IsEnabled="{Binding TextBoxEnabled}" Text="{Binding UpdateSourceTrigger=PropertyChanged, Path=SlavePoint1Name, ValidatesOnDataErrors=true, NotifyOnValidationError=true}"/>
While this is my second window class:
public partial class GeneratorWindow : Window, INotifyPropertyChanged
{
private readonly Validator validator = new Validator();
private int noOfErrorsOnScreen;
public GeneratorWindow()
{
this.InitializeComponent();
this.grid.DataContext = this.validator;
}
public int NumberOfPoints { private get; set; }
public int MainPDC { private get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private Boolean IsEnabled;
public Boolean TextBoxEnabled
{
get { return IsEnabled; }
set
{
IsEnabled = value;
NotifyPropertyChanged("TextBoxEnabled");
}
}
private void ValidationError(object sender, ValidationErrorEventArgs eventArgs)
{
if (eventArgs.Action == ValidationErrorEventAction.Added)
{
this.noOfErrorsOnScreen++;
}
else
{
this.noOfErrorsOnScreen--;
}
}
private void ValidationCanBeExecuted(object sender, CanExecuteRoutedEventArgs eventArgs)
{
eventArgs.CanExecute = this.noOfErrorsOnScreen == 0;
eventArgs.Handled = true;
}
private void ValidationExecuted(object sender, ExecutedRoutedEventArgs eventArgs)
{
// If the validation was successful, let's generate the files.
this.Close();
eventArgs.Handled = true;
}
}
For now, what I'm getting back is that my window is disabled (can't select any TextBox) and, obviously, this:
System.Windows.Data Error: 40 : BindingExpression path error: 'TextBoxEnabled' property not found on 'object' ''Validator' (HashCode=14499481)'. BindingExpression:Path=TextBoxEnabled; DataItem='Validator' (HashCode=14499481); target element is 'TextBox' (Name='tbSlave1'); target property is 'IsEnabled' (type 'Boolean')
From what I can understand, the culprit is the way I'm managing the DataContext in my class constructor. I probably need to add something to the validator line or totally change it, but I can't understand how.
I think you should be fine if you set the DataContext to the GeneratorWindow and updated you bindings accordingly. For that to work you need to change Validator to a public property.
Changed Validator definition:
public Validator Validator { get; } = new Validator();
DataContext:
this.grid.DataContext = this;
Updated Binding:
<TextBox x:Name="tbSlave1"
Validation.Error="ValidationError"
IsEnabled="{Binding TextBoxEnabled}"
Text="{Binding UpdateSourceTrigger=PropertyChanged,
Path=Validator.SlavePoint1Name,
ValidatesOnDataErrors=true,
NotifyOnValidationError=true}"/>
I have, till now, five different UserControls and the number can increase in future. I want to add them to a grid using MVVM pattern.
I have a class Apparatus that could serve as a viewModel for the UserControl
public class Apparatus
{
private string _appratusName;
private string _imageSource;
private string _experiments;
private GridProperties gridProps;
public Apparatus()
{
_appratusName = "";
_imageSource = "";
Experiments = "";
}
public Apparatus(string name, string imgSource, string exp)
{
_appratusName = name;
_imageSource = imgSource;
_experiments = exp;
}}
GridProperties is another class for containing the position of the UserControls in a grid.
public class GridProperties
{
public int Row;
public int Column;
public int ColumnSpan;
public int RowSpan;
public GridProperties(int row, int col, int rowSpan, int columnSpan)
{
this.Row = row;
this.Column = col;
this.RowSpan = rowSpan;
this.ColumnSpan = columnSpan;
}
there are public properties for each of the fields in the classes.
and also there's a ExperimentGridViewModel that's like
public class ExperimentGridViewModel : ViewModelBaseClass
{
private int _apparatusCount;
private string _workingExperiment;
private int _gridRowCount;
private int _gridColumnCount;
private ExperimentWindowViewModel _windowModel;
private ObservableCollection<Apparatus> _apparatusAppplied;}
I want to add UserControls, that are basically apparatuses like Burette, pippete etc. using MVVM.
I've looked though some answers for this and found out that this can be done using DataTemplates and ItemsControl but the problem is I have only one data type i.e Apparatus for all of my UserControls, Do I need to create different classes for each user control or Is there something else that I can do.
Now all I can do is some thing like this.
<Grid.Resources>
<DataTemplate DataType="{x:Type model:Apparatus }">
<apparatuses:Burette></apparatuses:Burette>
</DataTemplate>
</Grid.Resources>
<ItemsControl ItemsSource="{Binding AppliedApparatus}" >
</ItemsControl>
This adds burettes to grid, I can't add anything else as everything apparatus is of Type Appatatus.
Also, can any one please show me how could I bind the GridProperties in my viewModel to the the attached grid properties of the UserControls.
I'm trying to set up a ComboBox with its options binded from a list of strings, its default selected value binded from a setting, and with an event handler for its selection changed.
I want to configure it all using XAML like so:
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}"
SelectionChanged="RouteFilter_SelectionChanged" />
But when I do that on startup it throws the error:
An unhandled exception of type
'System.Reflection.TargetInvocationException' occurred in
PresentationFramework.dll
If I only do some of it in XAML, then either set the SelectionChanged event or the ItemsSource programatically in C# like below it works fine. But I have a lot of these ComboBoxes so I would rather do it straight in the XAML.
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}" />
With this C#:
public IEnumerable<string> Routes
{
get { return LubricationDatabase.GetRoutes(); }
}
public string DefaultRoute
{
get { return MySettings.Default.DefaultRoute; }
set { } /* side question: without this, it throws a parse exception. Any idea why? */
}
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
RoutesComboBox.SelectionChanged += RouteFilter_SelectionChanged;
}
I've also tried the solution found here:
private string _defaultRoute;
public string DefaultRoute
{
get { return MySettings.Default.DefaultRoute; }
set
{
if (_defaultRoute != value)
{
_defaultRoute = value;
// this fires before `SelectedValue` has been
// updated, and the handler function uses that,
// so I manually set it here.
RoutesComboBox.SelectedValue = value;
SelectionChangedHandler();
}
}
}
Which is okay, but is pretty bulky and probably more work than is worth it when I can just programatically assign the SelectionChanged event.
Again if possible I'd like to do it all using XAML because I have a lot of these ComboBoxes and initializing them all like this in the C# will look awful.
Any ideas?
Why are you binding with SelectedItem when you're not going to update the item when a user changes their selection? Not sure what your event handler is doing, but I have a working solution just the way you wanted it.
In short, you need to keep track of the DefaultRoute using a backing field. Also, you need to notify the UI when the selected item changes in your view model; which by the way is something you don't seem to be doing, MVVM. You should only be hooking into the selection changed event if you plan on updating the view in some way. All other changes should be handled in your view models DefaultRoute setter
XAML
<ComboBox Name="RoutesComboBox"
ItemsSource="{Binding Routes}"
SelectedItem="{Binding DefaultRoute}"
SelectionChanged="RouteFilter_SelectionChanged" />
Code
public partial class MainWindow : Window, INotifyPropertyChanged
{
public IEnumerable<string> Routes
{
get
{
return new string[] { "a", "b", "c", "d" };
}
}
public string DefaultRoute
{
get
{
return _defaultRoute;
}
set
{
_defaultRoute = value;
// Handle saving/storing setting here, when selection has changed
//MySettings.Default.DefaultRoute = value;
NotifyPropertyChanged();
}
}
public MainWindow()
{
this.DataContext = this;
InitializeComponent();
DefaultRoute = MySettings.Default.DefaultRoute;
}
private string _defaultRoute;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void RouteFilter_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
public static class MySettings
{
public static class Default
{
public static string DefaultRoute = "a";
}
}