I'm pulling what's remaining of my hair out trying to get something to work in Silverlight that works out of the box in WPF.
I have some CheckBoxes on a form which represent items - The Checked property is bound to a bool in my ViewModel (one viewmodel per item). When the checkbox is checked it adds the item to a list in another ViewModel - before this happens I want to perform some validation (in my case count how many items are in the list in the other ViewModel, and if its reached a limit show the user a message) and if this validation fails don't add it to the list and uncheck the box. When it runs, after the validation check is done in the bool property setter I can see the value set back to false, but this is not reflected back in the CheckBox in the UI in Silverlight so the CheckBox remains checked. In WPF this issue does not occur.
The following code demonstrates the issue - for brevity instead of performing the validation I'm just always forcing the box to unchecked when it is checked.
XAML
<UserControl x:Class="CheckboxTest.SL.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" Margin="20"/>
</Grid>
</UserControl>
Code Behind
namespace CheckboxTest.SL
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.DataContext = new MainPageViewModel();
}
}
public class MainPageViewModel : INotifyPropertyChanged
{
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if (_isSelected) //if checked always uncheck
_isSelected = false; //this does not get reflected in UI in Silverlight
OnPropertyChanged("IsSelected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler changedEventHandler = this.PropertyChanged;
if (changedEventHandler == null)
return;
changedEventHandler((object)this, new PropertyChangedEventArgs(propertyName));
}
}
}
I know that people used to get round this issue when it was in WPF pre .NET 4.0 by either setting the property binding to Async (not allowed in Silverlight) or to implement a dummy IValueConverter (tried this and it had no effect in Silverlight).
Can anyone suggest a way to get the above to work in Silverlight please?
One way to do this could be to bind the IsEnabled property of the CheckBox to a boolean property in your ViewModel where you can perform the validation.
public bool CanEditCheckBox
{
get
{
// perform validation here
return list.Length < 100;
}
}
Edit
I tested your code, and it seems to work like intended.
Related
I'm trying to debug a strange error in a combobox bound to an itemssource and selecteditem. It's driving me crazy.
The problem arise when changing selected tabitem in which the combobox exists. (Actually it's only when changing the DataContext of the ComboBox). The SelectedItem-binding has a custom validationrule to give error if value is null.
The problem is that wpf calls my custom rule when switching tabitems (DataContext) and tries to validate a value of null, even though the selecteditem-source never is null. This is a problem.
This is a simplified case I made that shows the same error:
Set a breakpoint in NotNullValidationRule.Validate and see how WPF tries to validate SelectedItem as null even though it is not present in any of the view model instances.
UPDATE
After some more experimenting I've discovered that the TabControl actually is irrelevant. Even with a simple ComboBox and a button to toggle it's DataContext I get the exact same problem. I'm replacing the code example with a new version.
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace ComboBoxValidationBugTest
{
public partial class MainWindow : Window
{
private Test t1, t2;
public MainWindow()
{
InitializeComponent();
t1 = new Test();
t1.Items.Add("A");
t1.Items.Add("B");
t1.Items.Add("C");
t1.SelectedItem = "A";
t2 = new Test();
t2.Items.Add("B");
t2.Items.Add("C");
t2.Items.Add("D");
t2.SelectedItem = "B";
ComboBox1.DataContext = t1;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
ComboBox1.DataContext = ComboBox1.DataContext == t1 ? t2 : t1;
}
}
public class Test : INotifyPropertyChanged
{
private string _selectedItem;
private ObservableCollection<string> _items = new ObservableCollection<string>();
public ObservableCollection<string> Items
{
get
{
return _items;
}
}
public string SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public override string ToString()
{
return _selectedItem;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class NotNullValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value == null)
{
return new ValidationResult(false, "Value was null");
}
return new ValidationResult(true, null);
}
}
}
And the XAML:
<Window x:Class="ComboBoxValidationBugTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:comboBoxValidationBugTest="clr-namespace:ComboBoxValidationBugTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DockPanel>
<Button Content="Toggle DataContext" DockPanel.Dock="Top" Click="ButtonBase_OnClick" />
<ComboBox ItemsSource="{Binding Items}" VerticalAlignment="Top" x:Name="ComboBox1">
<ComboBox.SelectedItem>
<Binding Path="SelectedItem">
<Binding.ValidationRules>
<comboBoxValidationBugTest:NotNullValidationRule />
</Binding.ValidationRules>
</Binding>
</ComboBox.SelectedItem>
</ComboBox>
</DockPanel>
</Grid>
</Window>
The issue you are getting is due the fact TabControl in wpf is virtualized. Only the visible tab actually exists and is rendered with the selected item. So on switching tabitem, control invalidates the previous tab and renders the tab with newly selected item and hence triggers dependency property change and in turn your ValidationRule.
So basically you will have to turn off the Tab virtualization to fix this. There are some workarounds to solve this. But a good solution is provided in the article below:
http://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization
Okai, so I have had many bugs with comboBox, and found that order matters. Try this:
<ComboBox
SelectedValue="{Binding Aldersgrense, NotifyOnValidationError=true, ValidatesOnExceptions=true, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding ElementName=UserControl, Path=DataContext.AldersgrenseTyper, Mode=OneTime}"
DisplayMemberPath="Beskrivelse" SelectedValuePath="Verdi"
Style="{StaticResource ErrorStyle}"></ComboBox>
Set x:Name=UserControl. Some bugs will be introduced using SelectedItem, order of SelectedValue and ItemSource, Mode!=OneTime in Items, and ofc the binding itself. Hope this helps someone out there.
Apparently it's a problem with the order of how bindings are updated when a new DataContext is set. When the ItemsSource-binding gets a new DataContext it notices (in some cases) that the selected item is not present in the new list, which then goes about setting SelectedItem to null and also validates this. Then the SelectedItem-binding gets the same DataContext as ItemsSource, is updated to it's correct value but without any validation to clear out previously failed rules.
The moment I changed the order of the bindings it worked! (in xaml that is)
Basically, I am reading a value from an xml file and displaying that value in a text box. This text box is editable so the user can make changes to it and when the session ends that value gets stored back in the xml file.
I know that the values correctly get stored and loaded to the text box. But I'm baffled as to why I cannot see the bound value that should be displayed in the text box..
This is the text box:
<UserControl x:Class="test.myView"
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"
xmlns:ui="clr-namespace:test.myView"
xmlns:local="test.myControls;assembly=test.mycontrols">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Style.xaml"/>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel Margin="8">
<TextBlock Text="Starting URL"/>
<TextBox Margin="0,5" FontSize="12" Height="30" Width="360" Text="{Binding myValue, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Style="{StaticResource WaterMarkTextBox}" local:WaterMarkTextHelper.WatermarkText="ENTER Value" />
</StackPanel>
This is the INotifyPropertyChanged:
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaiseChangeNotification(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
This is the property im binding to:
public string myValue
{
get { return _settings.myValue; }
set { _settings.myValue= value; }
}
I think it maybe because the value of the text box to empty before the code reaches the part where the start url is set using data binding. Does there exist a validate command in wpf that forces it to view the most up to date value?
There is not enough information to know the exact problem, but there are a few things to check when this sort of things happen.
Check your Output window in Visual Studio. This will give you any binding errors. This will help solve some of the following problems:
Not having your property set to public
Typo with your property name
Not having the DataContext set
Make sure your DataContext implements the INotifyPropertyChanged interface. This is required by the implementation of WPF to update the binding. You would do this like the following:
public class YourDataContext : INotifyPropertyChanged
{
private object _myvalue;
public object myvalue
{
get
{
return _myvalue;
}
set
{
if (_myvalue == value)
return;
_myvalue = value;
OnPropertyChanged("myvalue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
You can also look up several strategies to simplify this, like INotifyPropertyChanged weaving or a BaseViewModel class.
Edit
If myvalue is in your ViewModel that inherits from your ViewModelBase then all you need to do is change the implementation of your property to:
public string myValue
{
get
{
return _settings.myValue;
}
set
{
if (_settings.myValue == value)
return;
_settings.myValue = value;
RaiseChangeNotification("myValue");
}
}
Try to add one dummy converter and see if binding is getting fired when you change value from UI textbox (Convert back method needs to be executed). Else take help of snoop (http://snoopwpf.codeplex.com/) This will help you check status of binding.
And add RaiseNotification in setter if you want to update data from source to target.
public string myValue
{
get { return _settings.myValue; }
set { _settings.myValue= value; RaiseChangeNotification("myValue") }
}
On a popup window I have a checkbox.IsChecked bound to a model, but I want to check its state from the xaml code behind when the window is displayed. When checking the checkbox by name in the code behind from the windows loaded event it is not set yet. There are some UI specific things that I need to do and that is why I need to know what the checkboxes value is when the window opens and i cannot perform this from the model that the checkbox.IsChecked is bound to.
The property on the model is set long before the popup window is opened, so it is not an issue of the binding not being there. I figured that once the Loaded event fires the window would be ready to use bindings and all, but this does not seem to be the case.
Xaml:
<RefinedRibbonControls:RefinedRibbonGroup Header="Show Graphs">
<StackPanel x:Name="panelVisibilities">
<CheckBox Content="Show/hide" x:Name="myCheckBox"
IsChecked="{Binding Path=Processor.Model.IsItemVisible}"
Click="GraphVisibilityClickEvent"
HorizontalAlignment="Left"/>
...etc
Property on model:
public bool IsItemVisible
{
get { return _isItemVisible ; }
set
{
if (_isItemVisible != value)
{
_isItemVisible = value;
_propertyChanger.FirePropertyChanged(this, m => m.IsItemVisible);
}
}
}
Event in Xaml codebehind:
private void WindowLoadedEvent(object sender, RoutedEventArgs e)
{
if(myCheckBox.IsChecked.Value)
{
// Do UI Related Stuff
}
}
The binding works fine and the values show up when the window is displayed, the problem is I cannot get the value of the binding in the window loaded event.
Edit: Possible solution I have found but I am not sure if its the best way.
I called the following method from the constructor on the xaml code behind.
private void SetupInitialVisibility()
{
//Fire after everything is loaded.
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
IEnumerable<CheckBox> elements = this.panelVisibilities.Children.OfType<CheckBox>().ToList();
foreach (CheckBox checkBox in elements)
{
if (checkBox.IsChecked != null && checkBox.IsChecked.Value == false)
{
//Do work
}
}
}));
}
found at: https://stackoverflow.com/a/1746975/1253746
Data binding is not done synchronously, but it is delayed. Check this msdn page on dispatcher priorities. It is done at a lower priority than normal window messages, but before rendering.
You could invoke a method on yourself with a lower priority than is defined for databinding, in this method you should be able to safely read the data bound value.
I would still find this ugly. I'd rather subscribe directly to PropertyChanged and check for this property, or even better, rewrite your "UI related code" as a data binding.
P.S. If you start consuming events, be sure to unsubscribe, or you might get memory leaks.
DataBinding should precede the Loaded event I think.
When and how do you set your DataContext? And you are positive that the viewmodel property is already set?
The following works, try to align your code with this if possible.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<CheckBox x:Name="myCheckBox" IsChecked="{Binding IsItemVisible}" />
</Grid>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
if (myCheckBox.IsChecked.Value)
{
//...
}
}
}
ViewModel:
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isItemVisible;
public bool IsItemVisible { get { return isItemVisible; } set { isItemVisible = value; OnPropertyChanged("IsItemVisible"); } }
public ViewModel()
{
this.IsItemVisible = true;
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I've stumbled over the following problem. I've got a checkbox whose IsChecked property is bound to a CLR property in my MainWindow class. Here's the source code.
Code behind (MainWindow.xaml.cs):
namespace MenuItemBindingTest {
public partial class MainWindow : Window, INotifyPropertyChanged {
private bool m_backedVariable = false;
public bool IsPressAndHoldEnabled {
get { return this.m_backedVariable; }
set {
this.m_backedVariable = value;
OnPropertyChanged("IsPressAndHoldEnabled");
MessageBox.Show("Item changed: " + this.m_backedVariable);
}
}
public MainWindow() {
InitializeComponent();
this.m_checkbox.DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
if (this.PropertyChanged != null) {
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
XAML code (MainWindow.xaml):
<Window x:Class="MenuItemBindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Binding Problem Test" Width="525" Height="350">
<DockPanel>
<CheckBox x:Name="m_checkbox"
IsChecked="{Binding IsPressAndHoldEnabled}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Content="Is Press and Hold enabled"/>
</DockPanel>
</Window>
The problem now is that the set accessor for the property IsPressAndHoldEnabled is never called (ie. the message box never shows) when the user checks or unchecks the check box. It does, however, work when I rename the property to something else - like IsPressAndHoldEnabled2.
My question now is: Why can't I use IsPressAndHoldEnabled as name for my property? Does this have anything to do with the property Stylus.IsPressAndHoldEnabled existing?
Interesting. I don't have answers why, but I have workarounds:
Seperating the IsPressAndHoldEnabled property out to a seperate ViewModel class worked, unless the class was derived from FrameworkElement.
Also, changing from a regular property to a Dependency Property in the same MainWindow class worked -- the DP changed callback fires.
Have you specified TwoWay as your binding mode? Although I think CheckBox.IsChecked defaults to TwoWay binding mode...
I think you may have messed up your binding context, so that it is not finding the IsPressAndHoldEnabled property. Bindings in WPF fail silently -- a royal pain if you ask me.
Check that the check-box is really bound to that property, and that the binding context really is your MainWindodw class object.
In WPF I have a collection of bool? values and I want to bind each of these to a separate checkbox programmatically. I want the bindings to be TwoWay so that changing the value of the individual item in the collection in code updates the check box and vice versa.
I have spent ages trying to figure out how to do this and I am completely stuck. With the following code the checkbox only gets the right value when the window is loaded and that's it. Changing the check box doesn't even update the value in the collection. (UPDATE: this appears to be a bug in .NET4 as the collection does get updated in an identical .NET3.5 project. UPDATE: Microsoft have confirmed the bug and that it will be fixed in the .NET4 release.)
Many thanks in advance for your help!
C#:
namespace MyNamespace
{
public partial class MyWindow : Window, INotifyPropertyChanged
{
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public List<bool?> myCollection = new List<bool?>
{ true, false, true, false, true, false };
public List<bool?> MyCollection
{
get { return myCollection; }
set { myCollection = value; }
}
}
}
XAML:
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}">
There are a few things that need changing here to get this to work. Firstly you'll need to wrap your boolean value in an object that implements the INotifyPropertyChanged interface in order to get the change notification that you are looking for. Currently you are binding to boolean values in your collection which do not implement the interface. To do this you could create a wrapper class like so :
public class Wrapper: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool val = false;
public bool Val
{
get { return val; }
set
{
val = value;
this.OnPropertyChanged("Val");
}
}
public Wrapper(bool val)
{
this.val = val;
}
}
You'll then want to create these objects in your form instead of a list of booleans. You may also want to use an observable collection instead of a list so that notification of items being added and removed are sent. This is shown below:
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private ObservableCollection<Wrapper> myCollection = new ObservableCollection<Wrapper>()
{new Wrapper(true), new Wrapper(false), new Wrapper(true)};
public ObservableCollection<Wrapper> MyCollection
{
get { return myCollection; }
}
The next thing to do is to display a list of check boxes in your ui. To do this WPF provides itemscontrols. ListBox is an itemscontrol so we can use this as a starting point. Set the itemssource of a listbox to be MyCollection. We then need to define how each Wrapper object is going to be displayed in the list box and this can be done with a datatemplate which is created in the windows resources. This is shown below :
<Window.Resources>
<DataTemplate x:Key="myCollectionItems">
<CheckBox IsChecked="{Binding Path=Val, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=MyCollection}" ItemTemplate="{StaticResource myCollectionItems}"></ListBox>
</Grid>
This should get you up and running with a simple demo of checkboxes that have values bound to a list of booleans.
What makes you think it's not working? It's working for me :)
Here's my test XAML:
<UniformGrid>
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}"/>
<ListBox ItemsSource="{Binding MyCollection}"/>
<Button Content="Test" Click="Button_Click"/>
</UniformGrid>
Here's my code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
}
(the rest is the same as yours)
I placed a breakpoint on Button_Click and checked MyCollection[0] it was updated according to the IsChecked value of the CheckBox.
Try changing your collection type from List<bool?> to ObservableCollection<bool?> perhaps that is the reason you think it's not working for you (the fact that changes to the collection are not reflected anywhere else in your view).
Change your List<bool?> to an ObservableCollection<bool?>. A List does not raise the change notifications that WPF needs to update the UI. An ObservableCollection does. This handles the case where the list entry is changed and the CheckBox needs to update accordingly.
In the other direction, it works for me even with a List<bool?> -- i.e. toggling the checkbox modifies the value in the collection. Your binding syntax is certainly correct.