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 have a listview which on mouse enter to a particular column, i try to launch a popup in viewmodel class by setting isOpen to true in MyAction2() function which gets called on when user enters mouse on that column of listview.
I observe that when the mouse-enter to that column.It calls my function (MyAction2() function in ViewModel, see code written below) but even on setting the isopen variable to true in MyAction2(), The set-get method of binded isOpen not get called. Now i feel there is problem in binding. Which normally should be correct i feel some thing is missing but i dont know what.
My Xaml (containing teh opup and the column in ListView which on mouse enter calls an event called MyAction2() in ViewModel):
<Grid>
<StackPanel>
<Popup Margin="10,10,0,13" Name="Popup1" IsOpen="{Binding PopUpLaunched,Mode=TwoWay}" Placement="Top" PopupAnimation="Fade" StaysOpen="True" HorizontalAlignment="Left" VerticalAlignment="Top" Width="194" Height="200" MinWidth="500" MinHeight="500">
<StackPanel>
<Border Background="Red">
<TextBlock Name="McTextBlock" Background="LightBlue"> This is popup text </TextBlock>
</Border>
</StackPanel>
</Popup>
</StackPanel>
</Grid>
ViewModel.cs
private bool popUpLaunched;
public bool PopUpLaunched {
get {
return popUpLaunched;
} //Get set never gets called even after the popUpLaunched=true in the MyAction2() call
set {
popUpLaunched = value;
OnPropertyChanged("PopUpLaunched");
}
}
private void MyAction2(object param) //The function which gets called on mouse event but do not pop ups the popup
{
popUpLaunched = true;
}
Whats wrong and where is wrong ?
You should set the PopupLaunched property instead of setting the popUpLaunched field for the setter to get called and the PropertyChanged event to get raised:
private void MyAction2(object param)
{
PopUpLaunched = true;
}
In order to implement such a binding, you can make that property a Dependency property like this
public static readonly DependencyProperty PopUpLaunched = DependencyProperty.Register(
"popUpLaunched", typeof(bool), typeof(MainPage), new PropertyMetadata(null));
public bool popUpLaunched
{
get { return (bool)GetValue(PopUpLaunched); }
set { SetValue(PopUpLaunched, value); }
}
If you are not working on the MainPage, change that typeof(MainPage) argument respectively. And adjust getter and setter for your needs.
To simplify my problem, in my app I want to change the user's input to all uppercase. So "foo" should be displayed as "FOO" when the TextBox loses focus.
My Xaml:
<Page x:Class="App12.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:App12"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.DataContext>
<local:MainViewModel />
</Page.DataContext>
<StackPanel Margin="10,50,10,10" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBox Text="{Binding Name1, Mode=TwoWay}" />
<TextBox Text="{x:Bind Path=vm.Name2, Mode=TwoWay}" />
<Button HorizontalAlignment="Center">Just a control for the TextBox to lose focus</Button>
</StackPanel>
</Page>
My ViewModel
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace App12
{
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
}
private string _name1 = "something";
public string Name1
{
get
{
return _name1;
}
set
{
_name1 = (string)value.ToUpper();
OnPropertyChanged();
}
}
private string _name2 = "something";
public string Name2
{
get
{
return _name2;
}
set
{
_name2 = (string)value.ToUpper();
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged( [CallerMemberName] string propertyName = null )
{
var handler = PropertyChanged;
handler?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
}
And my code-behind
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace App12
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
MainViewModel vm;
public MainPage()
{
this.InitializeComponent();
DataContextChanged += MainPage_DataContextChanged;
}
private void MainPage_DataContextChanged( FrameworkElement sender, DataContextChangedEventArgs args )
{
vm = (MainViewModel)DataContext;
}
}
}
When I use classical binding in a UWP app (First TextBox), this code doesn't work
I see the setter being called, OnNotifyPropertyChanged gets called as well, and the handler is not null. Variable _text gets assigned its new value just fine (all uppercase), but then I never see the getter of public variable Text called.
I've also tried a converter (with ConvertBack implemented), with the same result.
Using x:Bind however (Second TextBox), it does work.
In WPF this also works as expected.
Am I missing something or has Binding changed? According to what Microsoft tells us and what I've seen it shouldn't have.
I found another Q/A in Stackoverflow which says:
The problem here is that the binding system in UWP is "intelligent". For TwoWay bindings, changes to the target will automatically propagate to the source and in this scenario, binding system assumes that the PropertyChanged event will fire for corresponding property in source and it ignores these events. So even you have RaisePropertyChanged or NotifyPropertyChanged in you source, the TextBox still won't update.
BTW I can't figure out how to create a workaround for this problem with the classic TwoWay Binding.
I 'm having problem with TextBlock/TextBox binding. The TextBlock doesn't display the property's content. When I 'm debugging my app, property has content. How you can do it?
Xaml.
<TextBlock HorizontalAlignment="Left" Margin="730,191,0,0" TextWrapping="Wrap" Text="{Binding XmlContentFile, Mode=TwoWay}" VerticalAlignment="Top" Height="429" Width="465"/>
I was finding simple code in web, but I didn't find code.
Code property
public string XmlContentFile
{
get
{
return this.xmlContentFile;
}
set
{
this.xmlContentFile = value;
}
}
My DataContext
DataContext="{Binding Main, Source={StaticResource Locator}}">
Method load XML file to string variable
public async void XmlContentLoad()
{
if (selectFile != null)
{
try
{
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
StorageFile storageFile = await storageFolder.GetFileAsync(selectFile);
xmlFileTextContent = await FileIO.ReadTextAsync(storageFile);
}
catch (Exception)
{
throw new Exception("Bug");
}
}
}
The problem is that your XmlContentFile property doesn't raise any notifications when it's changed. Your ViewModel needs to implement INotifyPropertyChanged and raise an event whenever any property has changed.
It's likely that your view and its data bindings are getting setup and executed before XmlContentLoad completes (it's asynchronous). If the binding has already completed before the data is loaded, the only way the binding will happen again is if the property raises a notification that it has changed.
It's also worth pointing out that in your XmlContentLoad method you're setting the private variable and not the public property.
xmlFileTextContent = await FileIO.ReadTextAsync(storageFile);
Setting the private variable will never raise property change notification even if you have the setter code wired up to raise the notification. You'll either need to change XmlContentLoad to set the property and have the OnPropertyChanged notification in the setter (recommended) or you'll need to call OnPropertyChanged after you set the private variable (not recommended).
Hope that helps.
Dev support, design support and more awesome goodness on the way: http://bit.ly/winappsupport
Make sure you are setting the Binding Source correctly :
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded_1" >
<Grid>
<TextBlock Text="{Binding XmlContentFile, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#FFF3A3A3"/>
</Grid>
and make sure as well you are setting the value of your property in the right place :
public partial class MainWindow : Window,INotifyPropertyChanged
{
private string _xmlContentFile;
public string XmlContentFile
{
get
{
return _xmlContentFile ;
}
set
{
_xmlContentFile = value;
OnPropertyChanged("XmlContentFile");
}
}
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded_1(object sender, RoutedEventArgs e)
{
XmlContentFile = "New Value !";
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
True that this answer is not in MVVM, but that won't need much changings except that you will be needing to set your DataContext to your ViewModel.
My main page has the appbar and it is shared across different pages. I wrote the following code to open the appbar on the click of a gridview item.
XAML
<AppBar Opened="AppBar_Opened" IsOpen="{Binding IsAppBarOpen}">
Back end
private void Clock_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
App.ViewModel.SelectedClock = (Clock)ThemeGridView.SelectedItem;
App.WorldViewModel.IsAppBarOpen = true;
}
private void ThemeGridView_ItemClick(object sender, ItemClickEventArgs e)
{
App.ViewModel.SelectedClock = (Clock)ThemeGridView.SelectedItem;
App.WorldViewModel.IsAppBarOpen = true;
}
WorldViewModel
private bool _IsAppBarOpen;
public bool IsAppBarOpen
{
get { return _IsAppBarOpen; }
set { base.SetProperty(ref _IsAppBarOpen, value); }
}
GridView XAML
<GridView
Grid.Row="1"
Grid.Column="1"
x:Name="ThemeGridView"
ItemsSource="{Binding Clocks}"
ItemTemplate="{StaticResource WorldClockTemplate}"
SelectionChanged="Clock_SelectionChanged"
SelectionMode="None"
IsItemClickEnabled="True"
ItemClick="ThemeGridView_ItemClick"
>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
But the appbar is not popping up when i select the gridview item. There is no binding error so its really mysterious!
There is not way to bind IsOpen property according the msdn:
Note Binding to the IsOpen property doesn't have the expected results
because the PropertyChanged notification doesn't occur when the
property is set.
<AppBar Opened="AppBar_Opened" IsOpen="{Binding IsAppBarOpen, **Mode=TwoWay**}">
This works for me. I use MVVM Light Toolkit.
public bool AppBarIsOpen
{
get { return this._appBarIsOpen; }
set
{
if (this._appBarIsOpen == value) { return; }
this._appBarIsOpen = value;
this.RaisePropertyChanged("AppBarIsOpen"); // without INotifyPropertyChanged it doesn't work
}
}
<AppBar
IsSticky="True"
IsOpen="{Binding Path=AppBarIsOpen, Mode=TwoWay}">
Roman Weisert's answer correctly states the likely reason for it not working, although you also must make the binding two-way as Zack Weiner suggested (I'm not sure the reason for the latter since the binding is not working in the target-to-source direction anyway). The current value of AppBar.IsOpen may not be reflected by IsAppBarOpen of your view-model. When that's the case, and you try updating the value, it's possible that no PropertyChanged event is raised since you may not actually be updating a value. Instead, you may be just setting the value from false to false or from true to true. Most SetProperty method implementations do not raise the PropertyChanged event unless there is an actual change, and I presume yours is the same.
To fix the problem, consider modifying your view-model as follows:
public bool IsAppBarOpen
{
get { return _IsAppBarOpen; } //changes initiated from UI not reflected
set //not updated from UI
{
_IsAppBarOpen = value;
base.OnPropertyChanged();
}
}
bool _IsAppBarOpen;
The notable difference from your view-model's code, is that SetProperty is not called here so PropertyChanged is raised even when the backing store equals the newly introduced value. In case your base class differs, note that mine has an OnPropertyChanged method with the signature
void OnPropertyChanged( [CallerMemberName] string propertyName = null )
that serves to raise the PropertyChanged event.
I can see from your use of the code-behind, though, that you are not really following MVVM. If MVVM is not a concern to you, then you could forgo the IsAppBarOpen property altogether and just directly set AppBar.IsOpen. As someone who religiously adheres to MVVM, however, I do not recommend that you further head in that (sinful) direction.
I had the same issue and using Caliburn Micro for WinRT and with this code worked for me:
<AppBar IsOpen="{Binding AppBarsOpen}" Name="MainAppBar" Padding="10,0,10,0" AutomationProperties.Name="Bottom App Bar">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
<StackPanel x:Name="LeftPanel" Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left">
<Button Name="ShowFlyout" Style="{StaticResource BookmarksAppBarButtonStyle}" />
</StackPanel>
<StackPanel x:Name="RightPanel" Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right">
<Button Style="{StaticResource SaveAppBarButtonStyle}" />
</StackPanel>
</Grid>
</AppBar>
And that's your property in ViewModel:
public bool AppBarsOpen
{
get { return _appBarsOpen; }
set
{
if (value.Equals(_appBarsOpen)) return;
_appBarsOpen = value;
NotifyOfPropertyChange(() => AppBarsOpen);
}
}
Had the same issue, solved it by adding the Closed event and updating the ViewModel from the code behind. Saw no other way since TwoWay binding was not working as Roman pointed out.
XAML
<AppBar x:Name="BottomAppBar1"
AutomationProperties.Name="Bottom App Bar"
Closed="BottomAppBar1_Closed"
IsOpen="{Binding IsOpen, Mode=TwoWay}"
IsSticky="True">
C# Code behind
private void BottomAppBar1_Closed(object sender, object e)
{
MainViewModel vm = this.DataContext as MainViewModel;
vm.IsOpen = false;
}
C# MainViewModel
public const string IsOpenPropertyName = "IsOpen";
private bool isOpen = false;
/// <summary>
/// Sets and gets the IsOpen property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public bool IsOpen
{
get
{
return isOpen;
}
set
{
RaisePropertyChanging(IsOpenPropertyName);
isOpen = value;
RaisePropertyChanged(IsOpenPropertyName);
}
}
You should bind both IsOpen and IsSticky two way because otherwise you will get problems with for example having to tap two time to unselect an item (once to close the app bar and once for unselecting) and also it's the will help having your app bar behave more standarly (will prevent the app bar to pop down on tap when an item is selected).
To show the app bar you will need to do the following (the order is important):
this.IsAppBarSticky = true;
this.IsAppBarOpen = true;
and to hide it, do the following:
this.IsAppBarSticky = false;
this.IsAppBarOpen = false;
Another way to make this work without having to use a codebehind handler for app bar closed event:
public class AppBarClosedCommand
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(AppBarClosedCommand), new PropertyMetadata(null, CommandPropertyChanged));
public static void SetCommand(DependencyObject attached, ICommand value)
{
attached.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject attached)
{
return (ICommand)attached.GetValue(CommandProperty);
}
private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Attach click handler
(d as AppBar).Closed += AppBar_onClose;
}
private static void AppBar_onClose(object sender, object e)
{
// Get GridView
var appBar = (sender as AppBar);
// Get command
ICommand command = GetCommand(appBar);
// Execute command
command.Execute(e);
}
}
then in the XAML you can use it like :
common:AppBarClosedCommand.Command="{Binding AppBarClosedCommand}"
with the command function looking like:
public void OnAppBarClosed()
{
AppBarOpen = false;
}