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>
Related
I am trying to bind the "IsChecked" property on the ToggleButton to "ModelView.IsEnabled".
"ModelView.IsEnabled" is always "false"
but somehow the ToggleButton can still show as "Checked".
Is there anything wrong with the binding?
XAML
...
<Page.Resources>
<ModelView:ModelView x:Key="ModelView"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ToggleButton IsChecked="{Binding Source={StaticResource ModelView}, Path=IsEnabled, Mode=TwoWay}">
<TextBlock >UWP Toggle Button</TextBlock>
</ToggleButton>
</Grid>
...
ModelView.cs
using...
namespace App2
{
class ModelView : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler CanExecuteChanged;
private bool _isEnabled;
public bool IsEnabled
{
get {
return _isEnabled;
}
set
{
_isEnabled = false;
OnPropertyChanged("IsEnabled");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
Try this, it worked to me:
1. Xaml code changes:
<Grid>
<Grid.DataContext>
<soHelpProject:MainViewModel/>
</Grid.DataContext>
<ToggleButton IsChecked="{Binding IsToggled, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<TextBlock >UWP Toggle Button</TextBlock>
</ToggleButton>
</Grid>
regards,
In your class ModelView, change IsEnabled from this:
public bool IsEnabled
{
get {
return _isEnabled;
}
set
{
_isEnabled = false;
OnPropertyChanged("IsEnabled");
}
}
to this:
public bool IsEnabled
{
get {
return _isEnabled;
}
set
{
_isEnabled = value;
OnPropertyChanged("IsEnabled");
}
}
EDIT: If i use _isEnabled = !value; as you suggested, it still works, with button and state now showing opposite values:
EDIT 2: Now, if you want to properly test your binding, then you could add an extra regular button and do this:
private void button1_Click(object sender, RoutedEventArgs e)
{
myModelView.IsEnabled = !myModelView.IsEnabled;
}
so you can watch your ToggleButton switch between true and false every time you click Test Button. Please note that Test Button is not bound to anything, it's just for testing purposes. See corresponding XAML at the bottom.
The problem is that the way you're doing it, "forcing" IsEnabled to be always false, you're actually sabotaging your own code...:O)
And finally, it is not clear from your code when/where you're assigning your DataContext. Please see below how to do it.
XAML:
<Page.DataContext>
<local:MyModelView/>
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ToggleButton x:Name="toggleButton1" Content="ToggleButton" IsChecked="{Binding IsEnabled, Mode=TwoWay}" HorizontalAlignment="Center"/>
<TextBlock x:Name="textBlock1" Text="{Binding IsEnabled}" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="126,0,201,286" />
<Button x:Name="button1" Click="button1_Click" Margin="127,400,0,220" Content="Test Button" Height="35" />
</Grid>
Code-behind:
private void Page_Loaded(object sender, RoutedEventArgs e)
{
myModelView = new MyModelView();
this.DataContext = myModelView;
}
I've run into the same problem, be it not with a ToggleButton, but with a TextBox, where I wanted to format the text the user had entered.
In your case you want to change the IsChecked property in your viewmodel and have it reflected in the User Interface straight away (so always be unchecked). The reason you want that is of absolutely no importance.
The problem is that with UWP the getter of your property gets called as you would expect when you click the ToggleButton. The normal action for the ToggleButton is to change from unchecked to checked (and vice versa) and that is what happens in your case. But then you expect that NotifyPropetyChanged signals the control in the UI. And that's where it goes wrong. The getter never gets called when the setter is executed (including NotifyPropertyChanged), so the UI doesn't reflect what you did in your setter.
This is very different from what the TwoWay Binding used to do (and still does in WPF). So there is nothing wrong with your code, but it seems that the binding mechanism has changed, although Microsoft claims it didn't. If you would use x:Bind, it works fine, so hat might solve your problem.
To clarify things more I have taken your example and modified it slightly, to show the problem.
I've put a ToggleButton on the page with a TwoWay binding to a viewmodel, exactly as you did. Clicking on the ToggleButton will switch its state from checked to unchecked and vice versa, even though the setter in my viewmodel Always sets the property to false (so unchecked).
But I've also added a normal button, that I've bound to a command that also modifies the property that the ToggleButton is bound to. Clicking this button calls the setter on the property the ToggleButton is bound to. Of course the setter gets called just the same, but after that the binding to the ToggleButton gets called, so NotifyPropertyChanged in this case does cause a UI update.
If you use the debugger, you can see exactly what i mean.
So your problem can be solved by using x:Bind, or by figuring out another way to update the UI, which you shouldn't have to do if Binding was still working as it used to. Maybe Microsoft has implemented some kind of optimization that now destroys classic Binding.
No special things, just a MainPage and a viewmodel.
My code for MainPage.xaml
<Page x:Class="App10.MainPage"
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:local="using:App10"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:ViewModel x:Key="viewModel" />
</Page.Resources>
<Grid x:Name="mainGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Margin="10,20,10,0">
<Button
x:Name="Button"
Content="UWP Normal button"
Command="{Binding Source={StaticResource viewModel}, Path=SwitchIschecked}"
HorizontalAlignment="Stretch" />
<ToggleButton
x:Name="toggleButton"
Margin="0,10,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
IsChecked="{Binding Source={StaticResource viewModel}, Path=IsChecked,
Mode=TwoWay}">
<TextBlock>UWP Toggle Button</TextBlock>
</ToggleButton>
</StackPanel>
</Grid>
</Page>
The code for MainPage.xaml.cs
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace App10
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
}
}
And the code for ViewModel.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace App10
{
public class ViewModel : INotifyPropertyChanged
{
private bool _isChecked;
// property for TwoWay binding with ToggleButton
public bool IsChecked
{
get
{
return _isChecked;
}
set
{
// extra var just to check 'value'
var _value = value;
// now always set it to false
_isChecked = false;
// Try to pass value of _isChecked to user interface
// because there is no check whether the value really
// has changed
// But this only works if the setter is not being called
// directly from the control the property is bound to
OnPropertyChanged();
}
}
private ICommand _switchChecked;
// ICommand for normal button, binding to Command
// calls method to set Property for ToggleButton
public ICommand SwitchIschecked
{
get
{
if ( _switchChecked == null )
_switchChecked = new ChangeChecked( new Action( ChangeVar ));
return _switchChecked;
}
set
{
_switchChecked = value;
}
}
// This will set the property for the ToggleButton
private void ChangeVar()
{
IsChecked = !IsChecked;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged( [CallerMemberName] string propertyName = null )
{
var handler = PropertyChanged;
handler?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
/// <summary>
/// Quick class to implement ICommand
/// </summary>
class ChangeChecked : ICommand
{
Action _execute;
public ChangeChecked( Action execute )
{
_execute = execute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute( object parameter )
{
return true;
}
public void Execute( object parameter )
{
_execute();
}
}
}
IsEnabled property is indicating whether the user can interact with the control. IsPressed is readonly property. So IsChecked is probably what you need.
I got used to implement event handlers to set a specific control's property, by creating a checking method and calling it in every handler, like that:
private void checkProperties()
{
myButton.IsEnabled = !String.IsNullOrWhiteSpace(myTextBox.Text) && myComboBox.SelectedIndex > -1;
}
private void myTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
checkProperties();
}
private void myComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
checkProperties();
}
and
<Window x:Class="MyProgram.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Name="myTextBox" VerticalAlignment="Top" Width="120" TextChanged="myTextBox_TextChanged" />
<ComboBox Height="23" HorizontalAlignment="Left" Margin="0,38,0,0" Name="myComboBox" VerticalAlignment="Top" Width="120" SelectionChanged="myComboBox_SelectionChanged" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="45,84,0,0" Name="myButton" VerticalAlignment="Top" Width="75" IsEnabled="False" />
</Grid>
</Window>
But that gets pretty heavy and redundant when you have a property that depends on 10 or more other controls' properties (just think about a Wizard window and its "Next" button, which should only be enabled if every controls are valid).
Is there a way to modify a property to automatically change depending on other controls' properties?
I've read about Dependency Properties a bit, but I'm not sure I can, let's say, modify the "IsEnabled" property of myButton to meet my expectations.
You don't have to handle events. You should be using element binding, which automatically bind to target properties values. However, both properties in question should be a Dependancy property. It should work in your case.
See this example:
http://msdn.microsoft.com/en-us/library/system.windows.data.binding.elementname(v=vs.110).aspx
Consider using MVVM instead of code behind. Enabling a button is command logic that is testable, and could be handled very simply in the view model via an ICommand.CanExecute delegate. No binding would be required since WPF automatically calls CanExecute() when the UI changes.
class MyViewModel : INotifyPropertyChanged
{
public ICommand SomeCommand { get; private set; }
public string Text { get; set; } //INPC omited for brevity
public int SelectedIndex { get; set; } //INPC omited for brevity
public MyViewModel()
{
SomeCommand = new RelayCommand(DoSomeCommand, CanDoSomeCommand);
}
private void DoSomeCommand()
{
//Blah
}
private bool CanDoSomeCommand()
{
return !String.IsNullOrWhiteSpace(this.Text) && this.SelectedIndex > -1;
}
}
WPF Multibinding is probably what you need in similar situations - Multibinding WPF.
<Grid>
...
<Grid.Resources>
<somenamespace:ValidatorConverter x:Key="ValidatorConverterResource">
</Grid.Resources>
<TextBox Name="myTextBox" ... />
<ComboBox Name="myComboBox" ... />
<Button Name="myButton" ...>
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource ValidatorConverterResource}"
ConverterParameter="Left">
<Binding Path="Text"
ElementName="myTextBox" />
<Binding Path="SelectedIndex"
ElementName="{myComboBoxSelf}" />
</MultiBinding>
</Button.IsEnabled>
</Button>
</Grid>
But it won't actually ease the problem (it will just move it from code-behind to converters):
public class ValidatorConverter: IConverter
{
public object Converter( ...) //...
public object ConverterBack( ...) //...
}
About the validation goal
There is possibly some easy way to do it, but I don't know it, so the easiest way to at least consolidate the issue to where it belongs is to use bindings and MVVM design pattern.
In the model(viewmodel) make some IsValid property(and bind it to the Next IsEnabled directly or even better - through the command) , that will be updated according to the current values of bound properties.
public class WizardViewModel : INotifyPropertyChanged
{
// OnPropertyChanged(String) and PropertyChanged event
public String Text
{
get //..
set
{
this._Text = value;
Validate();
this.OnPropertyChanged("SelectedItem")
}
}
public Object SelectedItem
{
get //..
set
{
this._SelectedItem = value;
Validate();
this.OnPropertyChanged("SelectedItem")
}
}
public bool IsValid { // ...}
private void Validate()
{
if (String.IsNullOrEmpty(this._Text))
this.IsValid = false;
// ....
}
}
And in xaml:
<somenamespace:WizardViewModel x:Key="WizardViewModelInstance">
// ....
<Grid.DataContext="{StaticResource WizardViewModelInstance}">
// ....
<TextBox ... Text="{Binding Text}"/>
<ComboBox ... SelectedItem="{Binding SelectedItem}"/>
<Button ... IsEnabled="{Binding IsEnabled}"/>
Such solution will decouple your logic from view and put everything related to such validation into one place.
Also:
If you want to learn about WPF validation you may want to read http://msdn.microsoft.com/en-us/library/ms753962(v=vs.110).aspx
If you want to learn about WPF commands, read http://msdn.microsoft.com/en-us/magazine/dn237302.aspx
I've created a button, which is supposed to support wordwrapping. My XAML code for the button looks like this:
<Button x:Class="POS.TouchScreen.UI.Elements.TouchButtonWPF"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Height="23" HorizontalAlignment="Left" Name="buttonGrid" VerticalAlignment="Top" Width="75" BorderBrush="#FF8A97A9" Margin="4"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<TextBlock Name="ButtonTextBlock"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Text="{Binding ButtonText, Mode=TwoWay}"
TextWrapping="Wrap">
</TextBlock>
</Button>
I've implemented the property as shown below:
public static readonly DependencyProperty ButtonTextProperty =
DependencyProperty.Register("ButtonText", typeof(string), typeof(TouchButtonWPF), new UIPropertyMetadata("Button",new PropertyChangedCallback(OnButtonTextChanged), new CoerceValueCallback(OnCoerceButtonText)));
private static object OnCoerceButtonText(DependencyObject o, object value)
{
TouchButtonWPF button = o as TouchButtonWPF;
if (button != null)
return button.OnCoerceButtonText((string)value);
else
return value;
}
protected virtual string OnCoerceButtonText(string value)
{
return value;
}
private static void OnButtonTextChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
TouchButtonWPF button = o as TouchButtonWPF;
if (button != null)
button.OnButtonTextChanged((string)e.NewValue, (string) e.OldValue);
}
protected virtual void OnButtonTextChanged(string NewValue, string OldValue)
{
this.ButtonTextBlock.Text = NewValue;
}
public string ButtonText
{
get { return (string)GetValue(ButtonTextProperty); }
set { SetValue(ButtonTextProperty, value); }
}
Inserting an instance of the TouchButtonWPF looks like this
<tse:TouchButtonWPF ButtonText="OK" FontSize="16" Height="77" HorizontalAlignment="Left"x:Name="buttonOk" Width="85" />
This works perfectly and the button text appears correctly. However when i assign the ButtonText from C# code, the text is not updated. I'm assigning the variable as shown below.
touchButton.ButtonText = navButton.Caption;
Can anybody tell me what I'm doing wrong?
Note that the event handlers have been implemented when it didn't work initially, can't figure out if these eventhandlers are needed at all for the functionality i try to attain?
Look forward to read your replies :)
Your problem is that you are setting a Dependency property directly (this.ButtonTextBlock.Text = NewValue).
Up until you did this, the value of this.ButtonTextBlock.Text was set to a Binding. Replacing the binding with a local value deleted the binding, and the Text will no longer respond to the original binding expression.
replace - this.ButtonTextBlock.Text = Value;
with - this.ButtonTextBlock.SetCurrentValue(TextProperty, value);
This will set the value without blowing away your bindings
I have parent window which has textBox called "SchoolName", and a button called "Lookup school Name".
That Button opens a child window with list of school names. Now when user selects school Name from child window, and clicks on "Use selected school" button. I need to populate selected school in parent view's textbox.
Note: I have adopted Sam’s and other people’s suggestion to make this code work. I have updated my code so other people can simply use it.
SelectSchoolView.xaml (Parent Window)
<Window x:Class="MyProject.UI.SelectSchoolView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Parent" Height="202" Width="547">
<Grid>
<TextBox Height="23" Width="192"
Name="txtSchoolNames"
Text="{Binding Path=SchoolNames, UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}"
/>
<Label Content="School Codes" Height="28" HorizontalAlignment="Left"
Margin="30,38,0,0" Name="label1" VerticalAlignment="Top" />
<Button Content="Lookup School Code" Height="30" HorizontalAlignment="Left"
Margin="321,36,0,0" Name="button1" VerticalAlignment="Top" Width="163"
Command="{Binding Path=DisplayLookupDialogCommand}"/>
</Grid>
</Window>
SchoolNameLookup.xaml (Child Window for Look up School Name)
<Window x:Class="MyProject.UI.SchoolNameLookup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="SchoolCodeLookup" Height="335" Width="426">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="226*" />
<RowDefinition Height="70*" />
</Grid.RowDefinitions>
<toolkit:DataGrid Grid.Row="0" Grid.Column="1" x:Name="dgSchoolList"
ItemsSource="{Binding Path=SchoolList}"
SelectedItem="{Binding Path=SelectedSchoolItem, Mode=TwoWay}"
Width="294"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
CanUserSortColumns="True"
SelectionMode="Single">
<Button Grid.Row="1" Grid.Column="1" Content="Use Selected School Name"
Height="23" Name="btnSelect" Width="131" Command="{Binding
Path=UseSelectedSchoolNameCommand}" />
</Grid>
</Window>
SchoolNameLookupViewModel
private string _schoolNames;
public string SchoolNames
{
get { return _schoolNames; }
set
{
_schoolNames= value;
OnPropertyChanged(SchoolNames);
}
}
private ICommand _useSelectedSchoolNameCommand;
public ICommand UseSelectedSchoolNameCommand{
get
{
if (_useSelectedSchoolNameCommand== null)
_useSelectedSchoolNameCommand= new RelayCommand(a =>
DoUseSelectedSchollNameItem(), p => true);
return _useSelectedSchoolNameCommand;
}
set
{
_useSelectedSchoolNameCommand= value;
}
}
private void DoUseSelectedSchoolNameItem() {
StringBuilder sfiString = new StringBuilder();
ObservableCollection<SchoolModel> oCol =
new ObservableCollection<SchoolModel>();
foreach (SchoolModel itm in SchollNameList)
{
if (itm.isSelected) {
sfiString.Append(itm.SchoolName + "; ");
_schoolNames = sfiString.ToString();
}
}
OnPropertyChanged(SchoolNames);
}
private ICommand _displayLookupDialogCommand;
public ICommand DisplayLookupDialogCommand
{
get
{
if (_displayLookupDialogCommand== null)
_displayLookupDialogCommand= new
RelayCommand(a => DoDisplayLookupDialog(), p => true);
return _displayLookupDialogCommand;
}
set
{
_displayLookupDialogCommand= value;
}
}
private void DoDisplayLookupDialog()
{
SchoolNameLookup snl = new SchoolNameLookup();
snl.DataContext = this; //==> This what I was missing. Now my code works as I was expecting
snl.Show();
}
My solution is to bind both the windows to the same ViewModel, then define a property to hold the resulting value for codes, lets call it CurrentSchoolCodes, Bind the label to this property. Make sure that CurrentSchoolCodes raises the INotifyPropertyChanged event.
then in the DoUseSelectedSchoolNameItem set the value for CurrentSchoolCodes.
For properties in your models I suggest you to load them as they are required(Lazy Load patttern). I this method your property's get accessor checks if the related field is still null, loads and assigns the value to it.
The code would be like this code snippet:
private ObservableCollection<SchoolModel> _schoolList;
public ObservableCollection<SchoolModel> SchoolList{
get {
if ( _schoolList == null )
_schoolList = LoadSchoolList();
return _schoolList;
}
}
In this way the first time your WPF control which is binded to this SchoolList property tries to get the value for this property the value will be loaded and cached and then returned.
Note: I have to say that this kind of properties should be used carefully, since loading data could be a time consuming process. And it is better to load data in a background thread to keep UI responsive.
The Solution Sam suggested here is a correct one.
What you didn't get is that you should have only one instance of you viewmodel and your main and child page should refer to the same one.
Your viewmodel should be instanciated once: maybe you need a Locator and get the instance there... Doing like this the code in your ctor will fire once, have a look at the mvvmLight toolkit, I think it will be great for your usage, you can get rid of those Classes implementing ICommand too...
You can find a great example of using that pattern here:
http://blogs.msdn.com/b/kylemc/archive/2011/04/29/mvvm-pattern-for-ria-services.aspx
basically what happens is this:
you have a Locator
public class ViewModelLocator
{
private readonly ServiceProviderBase _sp;
public ViewModelLocator()
{
_sp = ServiceProviderBase.Instance;
// 1 VM for all places that use it. Just an option
Book = new BookViewModel(_sp.PageConductor, _sp.BookDataService);
}
public BookViewModel Book { get; set; }
//get { return new BookViewModel(_sp.PageConductor, _sp.BookDataService); }
// 1 new instance per View
public CheckoutViewModel Checkout
{
get { return new CheckoutViewModel(_sp.PageConductor, _sp.BookDataService); }
}
}
that Locator is a StaticResource, in App.xaml
<Application.Resources>
<ResourceDictionary>
<app:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</ResourceDictionary>
</Application.Resources>
in your views you refer you viewmodels trough the Locator:
DataContext="{Binding Book, Source={StaticResource Locator}}"
here Book is an instance of BookViewModel, you can see it in the Locator class
BookViewModel has a SelectedBook:
private Book _selectedBook;
public Book SelectedBook
{
get { return _selectedBook; }
set
{
_selectedBook = value;
RaisePropertyChanged("SelectedBook");
}
}
and your child window should have the same DataContext as your MainView and work like this:
<Grid Name="grid1" DataContext="{Binding SelectedBook}">
I'm playing around with WPF Binding and variables. Apparently one can only bind DependencyProperties. I have come up with the following, which works perfectly fine:
The code-behind file:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public string Test
{
get { return (string)this.GetValue(TestProperty); }
set { this.SetValue(TestProperty, value); }
//set { this.SetValue(TestProperty, "BBB"); }
}
public static readonly DependencyProperty TestProperty = DependencyProperty.Register(
"Test", typeof(string), typeof(MainWindow), new PropertyMetadata("CCC"));
private void button1_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(Test);
Test = "AAA";
MessageBox.Show(Test);
}
}
XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<TextBox Height="31" HorizontalAlignment="Left" Margin="84,86,0,0" Name="textBox1" VerticalAlignment="Top" Width="152"
Text="{Binding Test, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}"/>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="320,85,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<TextBox Height="31" HorizontalAlignment="Left" Margin="84,138,0,0" Name="textBox2" Text="{Binding Test, Mode=TwoWay}" VerticalAlignment="Top" Width="152" />
</Grid>
The two TextBoxes update one an other. And the Button sets them to "AAA".
But now I replaced the Setter function with the one that is commented out (simulating some manipulation of the given value). I would expect that whenever the property value is changed it will be reset to "BBB". It does so when you press the button, that is when you set the property in code. But it does for some reason not affect the WPF Bindings, that is you can change the TextBox contents and thus the property, but apparently the Setter is never called.
I wonder why that is so, and how one would go about to achive the expected behaviour.
The CLR Property wrapper for a Dependency Property is never guaranteed to be called and therefore, you should never place any additional logic there. Whenever you need additional logic when a DP is changed, you should use the property changed callback.
In your case..
public string Test
{
get { return (string)this.GetValue(TestProperty); }
set { this.SetValue(TestProperty, value); }
}
public static readonly DependencyProperty TestProperty =
DependencyProperty.Register("Test",
typeof(string),
typeof(MainWindow),
new PropertyMetadata("CCC", TestPropertyChanged));
private static void TestPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
MainWindow mainWindow = source as MainWindow;
string newValue = e.NewValue as string;
// Do additional logic
}
Your change will not affect the binding because the XAML will call SetValue directly, instead of calling your property setter.That is how the dependency property system works.When a dependency property is registered a default value can be specified.This value is returned from GetValue and is the default value for your property.
Check the link below and read through to Robert Rossney's post to get a fair overview
WPF: What distinguishes a Dependency Property from a regular CLR Property?
also don't miss
http://msdn.microsoft.com/en-us/library/ms753358.aspx
and
http://msdn.microsoft.com/en-us/library/ms752914.aspx
Also note that unlike in normal CLR properties any custom logic you write in the setter will not be executed in Dependency Properties,instead you have to use the PropertyChangedCallback mechanism
http://blogs.msdn.com/b/delay/archive/2010/03/23/do-one-thing-and-do-it-well-tip-the-clr-wrapper-for-a-dependencyproperty-should-do-its-job-and-nothing-more.aspx