I am working on an app that has a lot of buttons on the main window.
The buttons have been programmed individually to change color when pressed, and save that those colors using the user settings from Visual Studio.
More exactly, when the user presses a button once, its background changes to red, and when he presses it again the background changes to green.
Edited for mm8:
Here is the xaml (sample):
<Window x:Class="test2.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:test2"
xmlns:properties="clr-namespace:test2.Properties"
mc:Ignorable="d"
Title="MainWindow" WindowStartupLocation="CenterScreen" Height="850" Width="925">
<Grid x:Name="theGrid">
<Button x:Name="Button0" HorizontalAlignment="Left" Margin="197,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Source={x:Static properties:Settings.Default}, Path=Color0, Mode=TwoWay}" Click="Button0_Click"/>
<Button x:Name="Button1" HorizontalAlignment="Left" Margin="131,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Source={x:Static properties:Settings.Default}, Path=Color1, Mode=TwoWay}" Click="Button1_Click"/>
<Button x:Name="Button2" HorizontalAlignment="Left" Margin="263,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Source={x:Static properties:Settings.Default}, Path=Color2, Mode=TwoWay}" Click="Button2_Click"/>
<Button x:Name="Reset" Content="Reset" HorizontalAlignment="Left" Margin="832,788,0,0" VerticalAlignment="Top" Width="75" Click="Reset_Click" />
</Grid>
</Window>
And this is the code I implemented into each button's click event:
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 System.IO;
namespace test2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button0_Click(object sender, RoutedEventArgs e)
{
if (Properties.Settings.Default.Color0 == "Green")
{
Properties.Settings.Default.Color0 = "Red";
Properties.Settings.Default.Save();
}
else
{
Properties.Settings.Default.Color0 = "Green";
Properties.Settings.Default.Save();
}
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
if (Properties.Settings.Default.Color1 == "Green")
{
Properties.Settings.Default.Color1 = "Red";
Properties.Settings.Default.Save();
}
else
{
Properties.Settings.Default.Color1 = "Green";
Properties.Settings.Default.Save();
}
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
if (Properties.Settings.Default.Color2 == "Green")
{
Properties.Settings.Default.Color2 = "Red";
Properties.Settings.Default.Save();
}
else
{
Properties.Settings.Default.Color2 = "Green";
Properties.Settings.Default.Save();
}
}
private void Reset_Click(object sender, RoutedEventArgs e)
{
foreach (Button button in theGrid.Children.OfType<Button>())
}
}
}
Now, I want to some sort of a Reset button, which when pressed changes the background of all the buttons to the default (not red, nor green).
What I tried to do was to use ideas from this thread and use them as a click event on the reset button, but whenever I do
foreach (Control x in Control.Controls)
or any other method using the "Controls" (this.Controls, etc) I get it underlined with red, saying that the Control class does not have the definition.
Am I doing something wrong? Do you guys have any suggestions as to how I can program that button to change all buttons' background to default?
The short version: you're doing it wrong. I mean, I suspect you already knew that to some extent, because the code didn't work. But looking at your comment that says you'll have 240 buttons, you are really going about this the wrong way.
This answer is meant to walk you through three different options, each moving you closer to what is the best approach for dealing with this scenario.
Starting with your original effort, we can get the code you posted to work mostly as-is. Your main problem is that, having successfully obtained each Button child of your Grid, you cannot just set the Button.Background property. If you do, you will erase the binding that was set up in the XAML.
Instead, you need to reset the values in your source data, and then force the binding target to be updated (because the Settings object does not provide a WPF-compatible property-changed notification mechanism). You can accomplish this by changing your Reset_Click() method to look like this:
private void Reset_Click(object sender, RoutedEventArgs e)
{
Settings.Default.Color0 = Settings.Default.Color1 = Settings.Default.Color2 = "";
Settings.Default.Save();
foreach (Button button in theGrid.Children.OfType<Button>())
{
BindingOperations.GetBindingExpression(button, Button.BackgroundProperty)?.UpdateTarget();
}
}
This is not ideal. It would be much better to not have to access the binding state directly, and instead let WPF deal with updates. In addition, if you look at the debug output, for every time a button is set to the "default" state, a exception is being thrown. That's also not a very good situation.
These issues can be addressed. The first, by moving to an MVVM-style implementation, in which the state of the program is stored independently of the visual part of the program, with the visual part responding to changes in that state. The second, by adding some logic to coerce the invalid string value into something that WPF is happy with.
To accomplish this, it's helpful to have a couple of pre-made helper classes made, one for supporting the view model classes themselves directly, and one for representing a command (which is a better way to deal with user input than handling Click events directly). Those look like this:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class DelegateCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public DelegateCommand(Action execute) : this(execute, null) { }
public DelegateCommand(Action execute, Func<bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return _canExecute?.Invoke() ?? true;
}
public void Execute(object parameter)
{
_execute();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
These are just examples. The NotifyPropertyChangedBase class is mostly identical to what I use on a day-to-day basis. The DelegateCommand class is a stripped-down version of a more fully-featured implementation I use (mainly, it's missing support for command parameters, since they aren't needed in this particular scenario). There are lots of similar examples on Stack Overflow and the Internet, often built into a library designed to help with WPF development.
With those, we can define some "view model" classes that will represent the state of the program. Note that these classes have practically nothing in them that involves the view per se. The one exception being the use of DependencyProperty.UnsetValue, as a concession to simplicity. It is possible to get rid of even that, along with the "coerce" methods that support that design, as you'll see in the third example, after this one.
First, a view model to represent each individual button's state:
class ButtonViewModel : NotifyPropertyChangedBase
{
private object _color = DependencyProperty.UnsetValue;
public object Color
{
get { return _color; }
set { _UpdateField(ref _color, value); }
}
public ICommand ToggleCommand { get; }
public ButtonViewModel()
{
ToggleCommand = new DelegateCommand(_Toggle);
}
private void _Toggle()
{
Color = object.Equals(Color, "Green") ? "Red" : "Green";
}
public void Reset()
{
Color = DependencyProperty.UnsetValue;
}
}
Then a view model that holds the overall state of the program:
class MainViewModel : NotifyPropertyChangedBase
{
private ButtonViewModel _button0 = new ButtonViewModel();
public ButtonViewModel Button0
{
get { return _button0; }
set { _UpdateField(ref _button0, value); }
}
private ButtonViewModel _button1 = new ButtonViewModel();
public ButtonViewModel Button1
{
get { return _button1; }
set { _UpdateField(ref _button1, value); }
}
private ButtonViewModel _button2 = new ButtonViewModel();
public ButtonViewModel Button2
{
get { return _button2; }
set { _UpdateField(ref _button2, value); }
}
public ICommand ResetCommand { get; }
public MainViewModel()
{
ResetCommand = new DelegateCommand(_Reset);
Button0.Color = _CoerceColorString(Settings.Default.Color0);
Button1.Color = _CoerceColorString(Settings.Default.Color1);
Button2.Color = _CoerceColorString(Settings.Default.Color2);
Button0.PropertyChanged += (s, e) =>
{
Settings.Default.Color0 = _CoercePropertyValue(Button0.Color);
Settings.Default.Save();
};
Button1.PropertyChanged += (s, e) =>
{
Settings.Default.Color1 = _CoercePropertyValue(Button1.Color);
Settings.Default.Save();
};
Button2.PropertyChanged += (s, e) =>
{
Settings.Default.Color2 = _CoercePropertyValue(Button2.Color);
Settings.Default.Save();
};
}
private object _CoerceColorString(string color)
{
return !string.IsNullOrWhiteSpace(color) ? color : DependencyProperty.UnsetValue;
}
private string _CoercePropertyValue(object color)
{
string value = color as string;
return value ?? "";
}
private void _Reset()
{
Button0.Reset();
Button1.Reset();
Button2.Reset();
}
}
The important thing to note is that nowhere in the above does anything try to manipulate the UI objects directly, and yet you have everything there that you'd need to maintain the state of the program as controlled by the user.
With the view models in hand, all that's left is to define the UI:
<Window x:Class="WpfApp1.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:l="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Width="66" Height="26" Background="{Binding Button0.Color}" Command="{Binding Button0.ToggleCommand}"/>
<Button Width="66" Height="26" Background="{Binding Button1.Color}" Command="{Binding Button1.ToggleCommand}"/>
<Button Width="66" Height="26" Background="{Binding Button2.Color}" Command="{Binding Button2.ToggleCommand}"/>
</StackPanel>
<Button Content="Reset" Width="75" HorizontalAlignment="Right" VerticalAlignment="Bottom" Command="{Binding ResetCommand}"/>
</Grid>
</Window>
Some things to note here:
There is no code at all in the MainWindow.xaml.cs file. It's completely unchanged from the default template, with just the parameterless constructor and the call to InitializeComponent(). By moving to an MVVM-style implementation, a lot of the internal plumbing required otherwise just goes away completely.
This code does not hard-code any UI element locations (e.g. by setting Margin values). Instead, it takes advantage of WPF's layout features to place the color buttons in a row in the middle, and to place the reset button in the lower right of the window (that way it's visible no matter what size the window is).
The MainViewModel object is set as the Window.DataContext value. This data context is inherited by any elements within the window, unless overridden by setting it explicitly, or (as you'll see in the third example) because the element is automatically generated in a different context. Binding paths are all relative to this object, of course.
Now, this would probably an okay way to go if you really did only have three buttons. But with 240, you're in for a lot of copy/paste headaches. There are a lot of reasons to follow the DRY ("don't repeat yourself") principle, including convenience and code reliability and maintainability. That all would definitely apply here.
To improve on the MVVM example above, we can do some things:
Save the settings in a collection instead of having an individual setting property for each button.
Maintain a collection of the ButtonViewModel objects instead of having an explicit property for each button.
Use an ItemsControl to present the collection of ButtonViewModel objects instead of declaring a separate Button element for every button.
To accomplish this, the view models will have to change a bit. The MainViewModel replaces the individual properties with a single Buttons property to hold all the button view model objects:
class MainViewModel : NotifyPropertyChangedBase
{
public ObservableCollection<ButtonViewModel> Buttons { get; } = new ObservableCollection<ButtonViewModel>();
public ICommand ResetCommand { get; }
public MainViewModel()
{
ResetCommand = new DelegateCommand(_Reset);
for (int i = 0; i < Settings.Default.Colors.Count; i++)
{
ButtonViewModel buttonModel = new ButtonViewModel(i) { Color = Settings.Default.Colors[i] };
Buttons.Add(buttonModel);
buttonModel.PropertyChanged += (s, e) =>
{
ButtonViewModel model = (ButtonViewModel)s;
Settings.Default.Colors[model.ButtonIndex] = model.Color;
Settings.Default.Save();
};
}
}
private void _Reset()
{
foreach (ButtonViewModel model in Buttons)
{
model.Reset();
}
}
}
You'll notice the handling of the Color property is a little different too. That's because in this example, the Color property is an actual string type instead of object, and I'm using an IValueConverter implementation to handle mapping the string value to what's needed by the XAML elements (more on that in a bit).
The new ButtonViewModel is a little different too. It has a new property, to indicate which button it is (this allows the main view model to know which element of the settings collection the button view model goes with), and the Color property handling is a little simpler, because now we're dealing only with string values, instead of the DependencyProperty.UnsetValue value as well:
class ButtonViewModel : NotifyPropertyChangedBase
{
public int ButtonIndex { get; }
private string _color;
public string Color
{
get { return _color; }
set { _UpdateField(ref _color, value); }
}
public ICommand ToggleCommand { get; }
public ButtonViewModel(int buttonIndex)
{
ButtonIndex = buttonIndex;
ToggleCommand = new DelegateCommand(_Toggle);
}
private void _Toggle()
{
Color = Color == "Green" ? "Red" : "Green";
}
public void Reset()
{
Color = null;
}
}
With our new view models, they can now be hooked up in the XAML:
<Window x:Class="WpfApp2.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:l="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Buttons}" HorizontalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<l:ColorStringConverter x:Key="colorStringConverter1"/>
<DataTemplate DataType="{x:Type l:ButtonViewModel}">
<Button Width="66" Height="26" Command="{Binding ToggleCommand}"
Background="{Binding Color, Converter={StaticResource colorStringConverter1}, Mode=OneWay}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
<Button Content="Reset" Width="75" HorizontalAlignment="Right" VerticalAlignment="Bottom" Command="{Binding ResetCommand}"/>
</Grid>
</Window>
As before, the main view model is declared as the Window.DataContext value. But, instead of explicitly declaring each button element explicitly, I'm using an ItemsControl element to present the buttons. It has these crucial aspects:
The ItemsSource property is bound to the Buttons collection.
The default panel used for this element would be a vertically-oriented StackPanel, so I've overridden that with a horizontally-oriented one, to achieve the same layout used in the previous examples.
I've declared an instance of my IValueConverter implementation as a resource so that it can be used in the template.
I've declared a DataTemplate as a resource, with the DataType set to the type of the ButtonViewModel. When presenting the individual ButtonViewModel objects, WPF will look in the in-scope resources for a template assigned to that type, and since I've declared one here, it will use that to present the view model object. For each ButtonViewModel object, WPF will create an instance of the content in the DataTemplate element, and will set the DataContext for the root object of that content to the view model object. And finally,
In the template, the binding uses the converter I declared earlier. This allows me to insert a little bit of C# code into the property binding, to allow me to ensure the string value is handled appropriately, i.e. when it's empty the appropriate DependencyProperty.UnsetValue is used, avoiding any runtime exceptions from the binding engine.
Here's that converter:
class ColorStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = (string)value;
return !string.IsNullOrWhiteSpace(text) ? text : DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
In this case, the ConvertBack() method is not implemented, because we'll only ever be using the binding in the OneWay mode. We just need to check the string value, and if it's null or empty (or whitespace), we return the DependencyProperty.UnsetValue instead.
Some other notes on this implementation:
The Settings.Colors property is set to type System.Collections.Specialized.StringCollection, and initialized (in the Designer) with three empty string values. The length of this collection determines how many buttons are created. You can, of course, use whatever mechanism you want to track this side of the data if you prefer something else.
With 240 buttons, simply arranging them in a horizontal row may or may not work for you (depending on how large the buttons really will be). You can use other panel objects for the ItemsPanel property; likely candidates include UniformGrid or ListView (with the GridView view), both of which can arrange the elements in an automatically spaced grid.
Since the Button elements are located in some kind of parent Panel, such as for example a StackPanel, you could iterate through its Children collection like this:
foreach(Button button in thePanel.Children.OfType<Button>())
{
//...
}
XAML:
<StackPanel x:Name="thePanel">
<Button x:Name="Button0" HorizontalAlignment="Left" Margin="197,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Source={x:Static properties:Settings.Default}, Path=Color0, Mode=TwoWay}" Click="Button0_Click" />
<Button x:Name="Button1" HorizontalAlignment="Left" Margin="131,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Source={x:Static properties:Settings.Default}, Path=Color1, Mode=TwoWay}" Click="Button1_Click" />
<Button x:Name="Button0_Copy" HorizontalAlignment="Left" Margin="563,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Color_0, Mode=TwoWay, Source={x:Static properties:Settings.Default}}" Click="Button0_Copy_Click"/>
<Button x:Name="Button1_Copy" HorizontalAlignment="Left" Margin="497,139,0,0" VerticalAlignment="Top" Width="66" Height="26" Focusable="False" Background="{Binding Color_1, Mode=TwoWay, Source={x:Static properties:Settings.Default}}" Click="Button1_Copy_Click"/>
</StackPanel>
I'm trying to reproduce a MVVM tutorial for WPF but applying it to UWP. But I've done everything in the tutorial I believe right the exact same code shown at the tutorial.
But when I ran the code I kept getting a StackOverflowException which is caused because the MainPageView keeps initializing again and again, until the exception is thrown.
The thing is I'm kinda knew at MVVM and I wish to master it, so can somebody please explain me why am I getting this?
I'll leave the code of each one of my classes and views.
This is my MainPageView.Xaml:
<Page
x:Class="MVVMHierarchiesDemo.MainPageView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHierarchiesDemo"
xmlns:views="using:MVVMHierarchiesDemo.Views"
xmlns:viewmodel="using:MVVMHierarchiesDemo.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<!--Anytime the current view model is set to an instance of a CustomerListViewModel,
it will render out a CustomerListView with the ViewModel is hooked up. It’s an order ViewModel,
it'll render out OrderView and so on.
We now need a ViewModel that has a CurrentViewModel property and some logic and commanding
to be able to switch the current reference of ViewModel inside the property.-->
<Page.DataContext>
<local:MainPageView/>
</Page.DataContext>
<Page.Resources>
<DataTemplate x:Key="CustomerTemplate" x:DataType="viewmodel:CustomerListViewModel">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate x:Key="OrderTemplate" x:DataType="viewmodel:OrderViewModel">
<views:OrderView/>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid x:Name="NavBar"
Grid.Row="0">
<Button Content="Customers"
Command="{Binding NavCommand}"
CommandParameter="customers"
Grid.Column="0"
Grid.Row="0"/>
<Button Content="Orders"
Command="{Binding NavCommand}"
CommandParameter="orders"
Grid.Column="2"
Grid.Row="0"/>
</Grid>
<Grid x:Name="MainContent"
Grid.Row="1">
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</Grid>
</Page>
This is my code-behind MainPageView.xaml.cs - here is where the StackoverflowException is thrown in the constructor it keeps calling it.
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace MVVMHierarchiesDemo
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPageView : Page
{
public MainPageView()
{
this.InitializeComponent();
}
}
}
This is my BindableBase.cs as the tutorial shows:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MVVMHierarchiesDemo
{
/*The main idea behind this class is to encapsulate the INotifyPropertyChanged implementation
* and provide helper methods to the derived class so that they can easily trigger the appropriate notifications.
* Following is the implementation of BindableBase class.*/
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void SetProperty<T>(ref T member, T val, [CallerMemberName]string propertyName = null)
{
if (object.Equals(member, val))
return;
member = val;
OnPropertyChanged(propertyName);
}
}
}
This is MyCommand.cs or better known as the relay command pattern:
using System;
using System.Windows.Input;
namespace MVVMHierarchiesDemo
{
/* Now it's time to actually start doing some view switching using our CurrentViewModel property.
* We just need some way to drive the setting of this property. And we're going to make it so that
* the end user can command going to the customer list or to the order view. First add a new class
* in your project which will implement the ICommand interface. Following is the implementation of
* ICommand interface.*/
public class MyCommand<T> : ICommand
{
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;
public MyCommand(Action<T> targetExecuteMethod)
{
_TargetExecuteMethod = targetExecuteMethod;
}
public MyCommand(Action<T> targetExecuteMethod, Func<T,bool> targetCanExecuteMethod)
{
_TargetExecuteMethod = targetExecuteMethod;
_TargetCanExecuteMethod = targetCanExecuteMethod;
}
public event EventHandler CanExecuteChanged = delegate { };
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter)
{
if (_TargetCanExecuteMethod != null)
{
T tparam = (T)parameter;
return _TargetCanExecuteMethod(tparam);
}
if (_TargetExecuteMethod != null)
return true;
return false;
}
void ICommand.Execute(object parameter)
{
if(_TargetExecuteMethod!=null)
{
T tparam = (T)parameter;
_TargetExecuteMethod(tparam);
}
}
}
}
This is my usercontrol for OrdersView.xaml:
<UserControl
x:Class="MVVMHierarchiesDemo.Views.OrderView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHierarchiesDemo.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<TextBlock Text="Order View"/>
</Grid>
</UserControl>
This is my user control CustomerListView.xaml:
<UserControl
x:Class="MVVMHierarchiesDemo.Views.CustomerListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVMHierarchiesDemo.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<TextBlock Text="Customer List View"/>
</Grid>
</UserControl>
This is my OrderViewModel:
namespace MVVMHierarchiesDemo.ViewModel
{
/*Derive all of your ViewModels from BindableBase class.*/
public class OrderViewModel : BindableBase
{
}
}
This is my CustomerViewModel:
namespace MVVMHierarchiesDemo.ViewModel
{
/*Derive all of your ViewModels from BindableBase class.*/
public class CustomerListViewModel : BindableBase
{
}
}
Finally this is my MainPageViewModel:
namespace MVVMHierarchiesDemo.ViewModel
{
/*Derive all of your ViewModels from BindableBase class.*/
public class MainPageViewModel : BindableBase
{
public MainPageViewModel()
{
NavCommand = new MyCommand<string>(OnNavigation);
}
private CustomerListViewModel _customerListViewModel = new CustomerListViewModel();
private OrderViewModel _orderViewModel = new OrderViewModel();
private BindableBase _currentViewModel;
public BindableBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
SetProperty(ref _currentViewModel, value);
}
}
public MyCommand<string> NavCommand { get; private set; }
private void OnNavigation(string destination)
{
switch (destination)
{
case "orders":
{
CurrentViewModel = _orderViewModel;
break;
}
case "customers":
default:
CurrentViewModel = _customerListViewModel;
break;
}
}
}
}
and lastly I think the MainPageView is the one causing the infinite looping but I don't understand why?
If somebody could be so kind to tell me what I am doing wrong on UWP?
Also I could use MVVM Light or MVVMCross I'm not interested on those solutions I want to learn MVVM by hand and later on i might check those frameworks.
It's because in your MainPageView.xaml you have this:
<Page.DataContext>
<local:MainPageView/>
</Page.DataContext>
So every MainPageview creates a nested MainPageView as its DataContext. These are created until you blow the stack.
I think you meant to put a MainPageViewModel in here.
I created a simple WPF MVVM app based on Caliburn.Micro for a testing purpose and it works fine: binding, events, etc.
But all that stops once I add a Telerik control to my View (in my case I used RadMap), so once I do it the events stop working at all.
Is this possible to use Caliburn along with Telerik or it's impossible? And if possible, what should I change?
I've never had a problem with telerik and Caliburn.Micro (used it in WPF and Silverlight)
Could you show your view and viewmodel, AppBootstrapper. I think something wrong with your Binding
Here are more details regarding my code:
The ViewModel:
public class TestViewModel: PropertyChangedBase
{
private string _name = "Superman1";
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyOfPropertyChange(() => Name);
}
}
private string _psition;
public string Position
{
get { return _psition; }
set
{
_psition = value;
NotifyOfPropertyChange(() => Position);
}
}
public void NameChanged()
{
Name = "Batman";
}
public void MouseMoveHandler(object sender, MouseEventArgs e)
{
Point p = e.GetPosition((Grid)sender);
Position = string.Format("X:{0} Y:{1}", p.X, p.Y);
}
}
The Bootstrapper:
public class AppBootstrapper : BootstrapperBase
{
public AppBootstrapper()
{
Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<TestViewModel>();
}
}
And here's the View:
<Grid cal:Message.Attach="[Event MouseMove] = [Action MouseMoveHandler($source, $eventArgs)]"
Width="500" Height="500" Background="LightBlue">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBox HorizontalAlignment="Center"
VerticalAlignment="Center"
Name="TextBoxControll"
Text="{Binding Name}"
FontSize="40"
Grid.Row="0"/>
<TextBox HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Position}"
FontSize="40"
Grid.Row="1"/>
<Button Grid.Row="2" FontSize="30"
cal:Message.Attach="[Event Click] = [Action NameChanged]">
Click Me</Button>
<telerik:RadMap Grid.Row="3"/>
</Grid>
So if I want my code to work, I have to remove all the Telerik related tags and (that's even not enought!) I also have to remove the references!
The code works only after everything telerik related gets removed.
If after that I simply drag radMap to the view, without even using it, caliburn stops working: the button click event and any other event, stops working
Did you add all references for rad map control?
Telerik.Windows.Controls
Telerik.Windows.Data
Telerik.Windows.Controls.DataVisualization
I just created a project using your code and everything work fine
Binding in Caliburn works for the "out of box controls" only everything else "REQUIRES" conventions to be created since Caliburn.Micro has no clue as to the nature of those controls.
Note: You absolutely don't need x:bind for Telerik controls they work just find with for example ItemsSource="{Binding SomeItems}"
But to get them working with x:Bind you will need something along these lines....
https://github.com/vcaraulean/Caliburn.Micro.Telerik/blob/master/Silverlight/Caliburn.Micro.Telerik/TelerikConventions.cs
NB: this not as exhaustive as you would hope. RadGrid is something you will never x:Bind (pretty much a guarantee, taking this one on would be time costly, with all of the crap you would have to come up with).
I've just started learning WPF desktop application. I've written some let say easy code below, to excercise binding operation.
The problem is:
I wanted type sth in TextBox and see it simultaneously in TextBlock, but after compiling and running app, controls on form do not behave as I described.
Can anybody help me to fix it?
MainWindow.xaml:
<Window x:Class="Napisy.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:Napisy"
xmlns:mv="clr-namespace:Napisy.ModelWidoku"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<mv:NapisyModelWidoku x:Key="napisyModelWidoku"/>
</Window.Resources>
<Grid DataContext="{StaticResource napisyModelWidoku}">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="1" Margin="10,10,10,10" Text="{Binding Path=Tekst,Mode=TwoWay}"/>
<TextBlock Grid.Row="2" Margin="10,10,10,10" Text="{Binding Path=Wyswietl,Mode=OneWay}"/>
</Grid>
</Window>
ViewModel code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Napisy.Model;
using System.ComponentModel;
namespace Napisy.ModelWidoku
{
public class NapisyModelWidoku : INotifyPropertyChanged
{
NapisyModel model = new NapisyModel();
public string Tekst
{
get
{
return model.Tekst;
}
set
{
model.Tekst = value;
OnPropertyChanged(nameof(Tekst));
OnPropertyChanged(nameof(Wyswietl));
}
}
public string Wyswietl
{
get
{
return model.Tekst;
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string nazwa)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nazwa));
}
}
}
Model Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Napisy.Model
{
public class NapisyModel
{
public string Tekst { get; set; }
}
}
EDIT:
Problem description,
before class NapisyModelWidoku, acces modificator public added,
added OnPropertyChanged(nameof(Tekst));
instead OnPropertyChanged("Wyswietl"); used OnPropertyChanged(nameof(Wyswietl));
After typing text into TextBox still TextBlock not refresh automatically. Still hope I receive tips. Thanks
Along with adding UpdateSourceTrigger in both bindings, make below change also,
public string Tekst
{
get
{
return model.Tekst;
}
set
{
model.Tekst = value;
OnPropertyChanged("Tekst");
OnPropertyChanged("Wyswietl");
}
}
I ran your code and it does not work because PropertyChanged is null. You have to set the datacontext of your view so that the PropertyChangedEventHandler can be binded.
Add in your code behind, i-e MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
this.DataContext = new NapisyModelWidoku();
}
I have the following:
MainWindow:
<Window x:Class="TestApp.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:TestApp"
xmlns:settings="clr-namespace:TestApp.Settings"
mc:Ignorable="d" Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl>
<ItemsControl.ItemsSource>
<Binding>
<Binding.Source>
<CollectionViewSource Source="{Binding Source={x:Static settings:CustomSettings.Default}, Path=coll}" />
</Binding.Source>
</Binding>
</ItemsControl.ItemsSource>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBox Text="{Binding Name}" />
<Button Grid.Column="1" Click="Button_Click" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
Code-Behind:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace TestApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
if (Settings.CustomSettings.Default.coll == null)
{
Settings.CustomSettings.Default.coll = new ObservableCollection<BasicClass>();
Settings.CustomSettings.Default.coll.Add(new BasicClass("String1"));
Settings.CustomSettings.Default.coll.Add(new BasicClass("String2"));
Settings.CustomSettings.Default.coll.Add(new BasicClass("String3"));
}
Settings.CustomSettings.Default.Save();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Settings.CustomSettings.Default.Save();
foreach (BasicClass item in Settings.CustomSettings.Default.coll)
{
MessageBox.Show(item.Name);
}
}
}
public class BasicClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string name;
public string Name
{
get { return name; }
set
{
if (value != name)
{
name = value;
NotifyPropertyChanged("Name");
}
}
}
public BasicClass() { }
public BasicClass(string Name)
{
this.Name = Name;
}
}
}
Settings:
using System.Collections.ObjectModel;
using System.Configuration;
using System.Diagnostics;
namespace TestApp.Settings
{
internal sealed partial class CustomSettings : ApplicationSettingsBase
{
private static CustomSettings defaultInstance = ((CustomSettings)(Synchronized(new CustomSettings())));
public static CustomSettings Default
{
get
{
return defaultInstance;
}
}
[UserScopedSetting()]
[DebuggerNonUserCode()]
public ObservableCollection<BasicClass> coll
{
get
{
return ((ObservableCollection<BasicClass>)(this["coll"]));
}
set
{
this["coll"] = value;
}
}
}
}
How it works:
The app present three controls consisting of a TextBox and a Button. These are part of an ItemsControl whose source is bound to a user setting 'coll', of type ObservableCollection<BasicClass>. BasicClass has one property, 'Name', which appears in the TextBox via data-binding.
Expected behaviour:
I change the text in a TextBox, then click the corresponding Button. This would then save the new value in 'coll', and then present a MessageBox sequence demonstrating that this has indeed been changed. I restart the app, and my value is showing the newly saved value.
Actual behaviour:
I change the text, I click the Button, the MessageBox sequence shows me that the value is now stored in the user settings (and should therefore have been saved). However, when I restart the app, I see the original value, not the saved one.
An anomaly(?):
If I click the button twice instead of once (going through the MessageBox sequence twice), when I restart the value has now been successfully saved.
EDIT (original answer below):
While I suspect implementing IBindableComponent on a subclass of ObservableCollection might work, I don't recommend it. If you're just storing strings, System.Collections.Specialized.StringCollection might help you.
But in general, I don't think it makes much sense to update your application settings each time something changes. Instead, load them during application startup into your view model (e.g. an ObservableCollection) and move them back to the application settings when closing. This way, the values only need to be deserialized and serialized once.
The effect you mention in your comment about setting the value to itself works (it seems) because the list is re-serialized when you set it. It appears, ApplicationSettingsBase is storing a serialized copy of every value you provide, and therefore can't react to changes in your original object. When you provide the value again, it overwrites its copy with a serialized version of the new state of your object. However, if you serialize the list every time a user makes a change, it will impact your app's performance, once the list gets longer.
This might be interesting to you as well:
How to store int[] array in application Settings
And it seems unnecessary to subclass ApplicationSettingsBase yourself, see https://social.msdn.microsoft.com/Forums/en-US/4e299ed8-8e3a-408e-b900-eb6738fe0775/persist-and-restore-application-state?forum=wpf
ORIGINAL:
I'm not familiar with the ApplicationSettingsBase, but perhaps this helps https://msdn.microsoft.com/en-in/library/8eyb2ct1(en-us).aspx:
You can only bind an application setting to a component that supports the IBindableComponent interface. Also, the component must implement a change event for a specific bound property, or notify application settings that the property has changed through the INotifyPropertyChanged interface. If the component does not implement IBindableComponent and you are binding through Visual Studio, the bound properties will be set the first time, but will not update. If the component implements IBindableComponent but does not support property change notifications, the binding will not update in the settings file when the property is changed.
It seems like it has to do with the fact that only serialized versions of your settings are stored and therefore changes within the list are not recognized (because the reference itself does not change). Note that when you replace the list entirely upon clicking a button, the new list's values are stored.