I have a problem while trying to bind 2 or more Comboboxes SelectedValue to a property, that is null.
Only 1 of the comboboxes bound to this property will show the real value.
Below is my Xaml where i use DataTemplate to select a Combobox for presentation of the viewModel.
Xaml:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:PropertyValueViewModel}">
<ComboBox SelectedValue="{Binding Value}" ItemsSource="{Binding SelectableValues}" DisplayMemberPath="Description" SelectedValuePath="Value"/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Label Content="These uses template:"></Label>
<ContentPresenter Content="{Binding ValueSelector}"></ContentPresenter>
<ContentPresenter Content="{Binding ValueSelector}"></ContentPresenter>
<ContentPresenter Content="{Binding ValueSelector}"></ContentPresenter>
</StackPanel>
And the code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ValueSelector = new PropertyValueViewModel()
{
SelectableValues = new List<SelectableValue>()
{
new SelectableValue("NULL", null),
new SelectableValue("1", 1)
},
Value = null
};
DataContext = this;
}
public static readonly DependencyProperty ValueSelectorProperty = DependencyProperty.Register(
"ValueSelector", typeof(PropertyValueViewModel), typeof(MainWindow), new PropertyMetadata(default(PropertyValueViewModel)));
public PropertyValueViewModel ValueSelector
{
get { return (PropertyValueViewModel)GetValue(ValueSelectorProperty); }
set { SetValue(ValueSelectorProperty, value); }
}
}
/// <summary>
/// My viewModel
/// </summary>
public class PropertyValueViewModel
{
public object Value { get; set; }
public object SelectableValues { get; set; }
}
/// <summary>
/// The items in the combobox
/// </summary>
public class SelectableValue
{
public SelectableValue(string header, object value)
{
Value = value;
Description = header;
}
public object Value { get; set; }
public string Description { get; set; }
}
Now i am wondering why only 1 of them can show the NULL value at startup?
I can change the value in any of them, and they will all sync with the value in the property - if i select 1 and then back to NULL, they will all show NULL.
It seems like its only the initial value is not shown correctly.
If i avoid using DataTemplate the binding works too.
Does anyone know why the DAtaTemplate behaves this way?
Interesting problem.
Fundamentally, this appears to be caused by your choice to use null as one of the selectable values. null, of course, has special meaning, for C#, .NET, and WPF. The problem also involves the order in which the initialization of the ComboBox element is done. The SelectedValuePath property is initialized after the SelectedValue property.
This means that as your program is starting up and the ComboBox elements are created, when null is assigned to the SelectedValue property through its binding, the ComboBox does not yet have enough information to handle that value as a legitimate item selection. Instead, it interprets it as no selection at all.
Why does the last ComboBox still get initialized the way you want? I'm not really sure…I didn't investigate very far regarding that. I could speculate, but the odds of my guessing correctly seem low so I won't bother. Since it's the anomaly and not necessarily in keeping with expected behavior (based on above, even if the behavior is the desired behavior) I'll chalk it up to one of WPF's many "quirks". :)
I found several work-arounds for the issue:
Don't use null as a selectable value. If every selectable value is non-null, then the non-null value used to initialize each element's SelectedValue property is retained and when the SelectedValuePath is initialized, the current selection for the ComboBox is set correctly.
Don't use SelectedValuePath. Instead, just bind to SelectedItem and initialize the Value property with the desired SelectableValue class instance (e.g. the first one in the list).
In the ComboBox's Loaded event, refresh the target of the binding.
The first two are significant departures from your current design. Personally, if at all possible I would go with one or the other. It seems to me that there's a clear danger in using null as a selectable value in a ComboBox, and this may not be the only oddity you run into. In the long run, maintenance of this part of the code may cost a lot more if you continue to use null.
That said, the third option does work, and if you're lucky, the only real hazard in using null is on initialization. My proposed work-around for that option would look something like this:
XAML:
<DataTemplate DataType="{x:Type local:PropertyValueViewModel}">
<ComboBox SelectedValue="{Binding Value}"
ItemsSource="{Binding SelectableValues}"
DisplayMemberPath="Description"
SelectedValuePath="Value"
Loaded="comboBox_Loaded"/>
</DataTemplate>
C#:
private void comboBox_Loaded(object sender, RoutedEventArgs e)
{
ComboBox comboBox = (ComboBox)e.OriginalSource;
BindingOperations.GetBindingExpression(comboBox, ComboBox.SelectedValueProperty)
.UpdateTarget();
}
This forces WPF to update the target (i.e. the SelectedValue property of the control). Since at this point, the SelectedValuePath has been set, assigning null to the property this time correctly updates the selected item for the ComboBox.
By the way, I would strongly recommend that you disambiguate the names of the Value properties in your models. Having two different Value properties used for bindings in a single XAML element is very confusing. I would use, for example, SelectedValue and ItemValue, for the PropertyValueViewModel class and the SelectableValue class, respectively.
Related
I have a custom control to show items with checkboxes inside a ComboBox. To realize this, I used a DataTemplate with a CheckBox. The ItemSource of the ComboBox uses a binding to a ObserableCollection<FilterValue> which contains my filter values. FilterValue is a custom class implementing INotifyPropertyChanged. The properties Content and IsChecked of the CheckBox use bindings as well to use the values of my list. This control will be used in Silverlight.
Binding itself works fine, as seen here:
The problem appears when I register the Checked or Unchecked event.
As soon as one of the check boxes changed its state, the event is fired as expected but at this moment, the value in the bound list is still not updated.
What I saw while debugging is that the Checked/Unchecked events are firing before the PropertyChanged event of the FilterValue.
This means that at the time the event is firing, I can't ask the list for all active (checked) filters. What could I do to achieve this?
FilterControl.xaml:
<UserControl
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:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:local="clr-namespace:Controls" x:Class="Controls.FilterControl"
mc:Ignorable="d"
d:DesignHeight="45" d:DesignWidth="140">
<StackPanel x:Name="LayoutRoot">
<sdk:Label x:Name="LblFilterDescription" Content="-" />
<ComboBox x:Name="Filter" Width="120" ItemsSource="{Binding AvailableFilters, RelativeSource={RelativeSource FindAncestor, AncestorType=local:FilterControl}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Path=Text}" IsChecked="{Binding Path=IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Checked="FilterChanged" Unchecked="FilterChanged" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</UserControl>
FilterControl.xaml.cs:
public partial class FilterControl : UserControl
{
public delegate void FilterChangedHandler(object sender);
public event FilterChangedHandler OnFilterChanged;
public ObservableCollection<FilterValue> AvailableFilters { get; set; }
public List<string> AppliedFilters
{
get
{
return new List<string>(AvailableFilters.Where(filter => filter.IsChecked).Select(filter => filter.Text));
}
}
public FilterControl()
{
InitializeComponent();
AvailableFilters = new ObservableCollection<FilterValue>();
}
public bool AddFilterValue(string filterValue)
{
bool found = false;
foreach (FilterValue f in AvailableFilters)
{
if (f.Text == filterValue)
{
found = true;
break;
}
}
if (!found)
AvailableFilters.Add(new FilterValue() { IsChecked = false, Text = filterValue });
return found;
}
private void FilterChanged(object sender, RoutedEventArgs e)
{
//Here if I check AvailableFilters, the value is not changed yet.
//PropertyChanged allways fires after this, what makes me unable to
//get all currently applied filters (checked items)...
}
}
FilterValue:
public class FilterValue : INotifyPropertyChanged
{
private bool _IsChecked;
private string _Text;
public bool IsChecked
{
get { return _IsChecked; }
set
{
_IsChecked = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public string Text
{
get { return _Text; }
set
{
_Text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Text"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So, as I tried to reproduce this behavior, I realized that this appears to be a behavior that only occurs like that in Silverlight. If you try this example on WPF, the Changed fires after the bound property is updated. So you can just access your AppliedFilters property in the FilterChanged method and it will reflect the actual current situation. On Silverlight though, not so much. Even worse, this behavior didn’t even appear to be consistent to me. I did encounter situations in which the event fired after the property has been updated (resulting in the expected output).
A way to get around this is to clean up your component logic. If you look at it, you are mixing two different concepts: Event-driven UI logic, and clear data binding. Of course, doing it “properly” has multiple effects you likely cannot just ensure in an existing project, but you can at least try to get in the right direction here which should then also solve this issue.
So your logic right now uses data binding to provide the data for the view, and to reflect changes of the displayed items. But you are using events on the item level to perform additional logic depending on the former changes. As we have seen, the order of execution appears not be guaranteed across platforms, so it’s best to avoid having to rely on it.
In this case, you should have your data be the source of truth and make changes in the data tell you when applied filters change. You’re already halfway there by having an ObservableCollection and items that implement INotifyPropertyChanged. Unfortunately, an observable collection will only notify you about changes to the collection but not to changes to the contained items. But there are multiple solutions to expand the collection to also look at the items inside the collection.
This related question covers exactly that topic and there are multiple ideas on how to expand the observable collection for exactly that behavior. In my case, I have used the FullyObservableCollection implementation by Bob Sammers.
All you have to do for that is to change your ObservableCollection<FilterValue> into a FullyObservableCollection<FilterValue> and subscribe to the ItemPropertyChanged event:
AvailableFilters = new FullyObservableCollection<FilterValue>();
AvailableFilters.ItemPropertyChanged += AvailableFilters_ItemPropertyChanged;
In that event handler, you will then correctly see the proper behavior.
In my UserControl ucStep2 I have DataContext of Step2InfoData object that has several properties along with :
private string rockDensUnit;
public string RockDensity_Unit
{
get { return rockDensUnit; }
set
{
if (rockDensUnit != value)
{
rockDensUnit = value;
Changed("RockDensity_Unit");
}
}
}
In my app I got to bind several combo's with different normally measurement types Like {kg/m3, gm/m3}, {meter, cm} and so on such groups of measures. I mean, multiple combo's to have list of same items. So I preferred to create Class's of such lists that I can use in multiple combos. I created ComboItems.cs which contains all items lists that I will need to populate the drop down.
ComboItems.cs
//**OBJECTS I USE FOR LIST OF IEMS**
// Class for kg, gm
public class KgGmItems
{
public ObservableCollection<string> KgGmList { get; set; }
public KgGmItems()
{
KgGmList = new ObservableCollection<string>();
KgGmList.Add("kg/m3");
KgGmList.Add("gram/cm3");
}
public string ValueSelected { get; set; } // Don't know if this is useful in my case
}
// Class for meter, cm
public class MtCmItems : INotifyPropertyChanged
{
public MtCmItems()
{
Dict = new Dictionary<string, string>
{
{"meter", "meter"},
{"centimeter", "centimeter"}
};
}
//...
}
XML i.e. ucStep2 View
<!-- As the objects KgGmItems doesn't contain in ucStep2.xaml.cs or Step2InfoData (that is bound to this UC) so add reference of those classes -->
<UserControl.Resources>
<ObjectDataProvider x:Key="KgGmObj" ObjectType="{x:Type top:KgGmItems}" />
<ObjectDataProvider x:Key="MtCmObj" ObjectType="{x:Type top:MtCmItems}" />
</UserControl.Resources>
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" SelectedValue="{Binding Path=RockDensity_Unit, Mode=TwoWay}" SelectedIndex="0"
Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
</ComboBox>
I want to display ObservableCllection KgGmList items from KgGmItems class and bind the selected value to RockDensity_Unit of class Step2InfoData that is bound to this UserControl.
In the above combo, I am able to display all items in the drop down, also 1st item is selected by default. But the value is not bind to RockDensity_Unit; it's value remains null.
I want this to happen 2-way i.e. when RockDensity_Unit proeprtiy's value is set programmatically, the value should be selected in the drop down. Of course the value should exists in the list.
By default the 1st item should be selected.
UPDATE
Added DependencyProperty in ucStep2.xaml.cs
public static readonly DependencyProperty RockDensityUnitProperty =
DependencyProperty.Register("RockDensity_Unit", typeof(string), typeof(UserControl),
new FrameworkPropertyMetadata("kg/m3", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string RockDensity_Unit
{
get { return this.GetValue(RockDensityUnitProperty) as string; }
set { SetValue(RockDensityUnitProperty, value); }
}
XML
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" SelectedItem="{Binding Path=RockDensity_Unit, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}"
Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
</ComboBox>
ERROR
Error 1 The type reference cannot find a public type named 'ucStep2'. Line 74 Position 194. This refers to the combobox ", "
after FindAncestor
DOUBT
The RockDensity_Unit CLR property in Step2InfoData is untouched.
Why is the code not able to find ucStep2 ? FYI, I think this may be relevant :
<UserControl x:Class="WellBore.ucStep2"
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:local="clr-namespace:WellBore.Models"
xmlns:top="clr-namespace:WellBore"
mc:Ignorable="d"
d:DesignHeight="870" d:DesignWidth="700" MaxHeight="970" MinHeight="700" MaxWidth="600">
Ok, so let's get this binding working... first, I am using an item from your KgGmItems class to bind to the ComboBox. In this class you have a collection of string values to display in the drop down and a string property to bind to the ComboBox.SelectedItem... perfect! Now I'm assuming that you have an instance of this class in the Resources section called KgGmObj... let's keep it simple to start with:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding ValueSelected, Mode=TwoWay}" />
This is all you need to setup the binding between the ComboBox and your class. One thing to note though, is that when you try to set the selected item from your code, it will only work if you set it to one of the actual items in the collection... I think that this doesn't really count when using strings, but it's important to know this anyway. If you were setting a custom class as the type of objects in the ComboBox instead, then you could set the selected item like this:
ValueSelected = KgGmList.Where(item => item.Name == "NameOfObjectToMatch").Single();
Or better like this if you had a uniquely identifiable property:
ValueSelected = KgGmList.Where(item => item.Id == Id).Single()
With your string values, you should be able to set the selected item from code like this:
ValueSelected = "Some value";
UPDATE >>> Ok, so let's have another go... I think that I may have enough information to go on now. I think that you want something like this:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding RockDensity_Unit, Mode=TwoWay}" />
The problem with this is that you have set the DataContext of the ComboBox to your KgGmObj object. This means that the Framework is going to try to find a property named RockDensity_Unit in that object. I also see another potential problem in your definition of this property.
In order to bind from a UserControl xaml to its code behind, you need to use a DependencyProperty. You can find out how to implement these from the Dependency Properties Overview page at MSDN. So first, I would recommend that you implement your RockDensity_Unit property as a DependencyProperty.
Next, we have to find a way to that property from the ComboBox in the xaml... we can do that using a RelativeSource binding like this:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding RockDensity_Unit, RelativeSource={RelativeSource Mode=
FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}" />
Now, if you have a DependencyProperty to bind to the SelectedItem property and your UserControl class is named ucStep2, this should all work... let me know how it goes.
UPDATE 2 >>>
Your error is because you have to add an XML namespace at the top of your XAML file... something like this:
xmlns:YourNamespace="clr-namespace:ApplicationName.FolderNameContainingClass"
Then you use it to reference your class like this:
...AncestorType={x:Type YourNamespace:ucStep2} ...
Also, in your DependencyProperty declaration, you're supposed to supply the name the type of your control, not UserControl, so change
Register("RockDensity_Unit", typeof(string), typeof(UserControl),
to
Register("RockDensity_Unit", typeof(string), typeof(NameOfYourUserControl),
Clearly... replace 'NameOfYourUserControl' with the actual name of your class that extends the UserControl.
Use a Dictionary.
XAML
<ComboBox ItemsSource="{Binding Dict}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Prop}"/>
Code Behind
public Dictionary< ValueType, string > Dict { get; private set; }
private ValueType _prop;
public ValueType Prop
{
get{ return _prop }
set
{
_prop = value;
NotifyPropertyChanged( "Prop" ); // Implement INotifyPropertyChanged
}
}
public ViewModel()
{
Dict = new Dictionary< ValueType, string >()
{
{ value1, string1 },
{ value2, string2 },
{ value3, string3 }
};
}
Ok, we are trying out XAML for our GUI now (and learning as we go)...I have been able to do the data binding without a problem in XAML and C# independent of one another, but now comes the time I need to pass values back and forth and I'm a bit lost. When I compile and try to navigate to the page, it is throwing a XamlParseException: Specified class name doesn't match actual root instance type. Remove Class directive or provide an instance via XamlObjectWriterSettings.RootObjectInstance. Line 5 position 2.
Any help or a gentle shove in the right direction is greatly appreciated :)
Here's where I am:
namespace TheAirline.GraphicsModel.PageModel.PageFinancesModel
{
/// <summary>
/// Interaction logic for PageFinances.xaml
/// </summary>
public partial class PageFinances : Page
{
private Airline Airline;
public PageFinances(Airline airline)
{
InitializeComponent();
this.Language = XmlLanguage.GetLanguage(new CultureInfo(AppSettings.GetInstance().getLanguage().CultureInfo, true).IetfLanguageTag);
this.Airline = airline;
Page page = null;
//loading the XAML
using (FileStream fs = new FileStream("TheAirline\\GraphicsModel\\PageModel \\PageFinancesModel\\PageFinances.xaml", FileMode.Open, FileAccess.Read))
{
page = (Page)XamlReader.Load(fs);
}
//finding XAML element and trying to set the value to a variable
string airlineCash = GameObject.GetInstance().HumanAirline.Money.ToString();
TextBox cashValue = (TextBox)page.FindName("cashValue");
cashValue.DataContext = airlineCash;
}
}
}
And the first few lines of the XAML:
<Page
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:AirlineModel="clr-namespace:TheAirline.Model.AirlineModel"
mc:Ignorable="d"
x:Class="TheAirline.GraphicsModel.PageModel.PageFinancesModel.PageFinances"
xmlns:c="clr-namespace:TheAirline.GraphicsModel.Converters"
...>
</Page>
Bindings in XAML are resolved against the object that is assigned to the DataContext property of any given XAML element. The value of that property (as well as many other properties) Is Inherited in any given Visual Tree from parent elements to child elements.
for instance, given this class:
public namespace MyNamespace
{
public class ViewModel
{
public string Name {get;set;}
public bool IsActive {get;set;}
}
}
and this XAML:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace"
FontSize="20">
<Window.DataContext>
<local:ViewModel>
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding Path=Name}"/>
<CheckBox IsChecked="{Binding Path=IsActive}"/>
<StackPanel>
</Window>
All four objects defined in XAML, the Window, the StackPanel, the TextBox, and the CheckBox, will have a FontSize of 20, and the instance of the ViewModel class assigned to their DataContext property. Therefore all bindings (Except bindings with a specified ElementName, RelativeSource, or Source) will be resolved against that instance.
It would be exactly the same if the property was assigned in code instead of in XAML:
public MyWindow() //Window Constructor
{
InitializeComponent();
this.DataContext = new ViewModel(); //Note that keyword "this" is redundant, I just explicity put it there for clarity.
}
Because of this, there is no need to set the DataContext property to each element explicitly, as the framework is already taking care of that.
Also, notice that in XAML, most built-in Markup Extensions have a default constructor convention that allows you to abbreviate their usage. In the case of the Binding Markup Extension, the default constructor has the Path property, therefore this:
<TextBox Text="{Binding Path=Name}"/>
is exactly the same as this:
<TextBox Text="{Binding Name}"/>
Now, for property changes in the underlying DataContext to be automatically passed from the binding source (ViewModel) to the binding target (XAML-defined objects), the source object must implement the System.ComponentModel.INotifyPropertyChanged interface and raise the PropertyChanged event every time a property changes.
Therefore, in order to support Two-Way Binding, the example class should look like this:
public namespace MyNamespace
{
public class ViewModel: INotifyPropertyChanged
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
private bool _isActive;
public bool IsActive
{
get
{
return _isActive;
}
set
{
_isActive = value;
NotifyPropertyChanged("IsActive");
}
}
}
public void NotifyPropertyChanged (string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
}
Notice that the ViewModel class has no dependency or direct reference to any of the XAML-defined objects, but still it contains the Values of the properties that will appear in the UI. This allows for a complete decoupling between UI and application logic/data known as the MVVM Pattern. I strongly suggest you research on that topic if you expect to be successful in programming in C# + XAML, because it is a radical mindshift when compared to other, traditional UI paradigms.
For example, something like this is not recommended in XAML-based applications:
if (myWindow.CheckBox1.IsChecked)
//Do Something
because that would mean that you're coupling the application logic and making it dependant on the state of UI elements, which is precisely what you need to avoid.
Notice that all the links and all the concepts referenced in this answer pertain to WPF, but are also applicable to Silverlight and WinRT. Since you did not specify which of the three XAML-based frameworks you're using, I posted the WPF ones, which is what I'm most familiar with.
I have made a tree View in wpf Using MVVM .
it is working fine but here is one problem that leaf node contains some checkboxes and user have only two options either to select one or none .
So here how i can restricted user to select maximum only one cold drink.
I did one trick but it didn't work that when i have already selected a drink and then i select another one than i set the last selected value in the observable collection to false but it doesn't affect on view and selected check boxes remains selected although in collection only one option's value is true.
I cant use radio button instedof checkbox becasue user can select none of the options and i cant give an additional option for none of the above.
If any one have any solution so please let me know I'll be very thankful.
updated question:
i think i didn't define my problem in a proper way so i am giving my code snipperts here hope by this i'll get the solution o f my problem...
My View Model Class
namespace TestViewModels
{
public class ViewModel :ViewModelBase
{
private ObservableCollection<AvailableProducts> _MyTreeViewProperty
public ObservableCollection<AvailableProducts> MyTreeViewProperty
{
get { return _MyTreeViewProperty
set { _MyTreeViewProperty value;
RaisePropertyChanged("MyTreeViewProperty");}
}
}
public class AvailableProducts
{
private string _BrandName;
public string BrandName
{
get { return _BrandName
set { _BrandName = value; }
}
private bool _IsExpanded;
public bool IsExpanded
{
get
{
return _IsExpanded;
}
set
{
_IsExpanded = value;
}
}
private ObservableCollection<ProductTypes> _MyProductTypes
public ObservableCollection<ProductTypes> MyProductTypes
{
get { return _MyProductTypes}
set { _MyProductTypes= value; }
}
}
public class ProductTypes
{
private string _ProductTypeName;
public string ProductTypeName
{
get { return _ProductTypeName;
set { _ProductTypeNamevalue; }
}
private ObservableCollection<ProductSubTypes> _ProdSubTypes;
public ObservableCollection<ProductSubTypes> ProdSubTypes
{
get { return _ProdSubTypes;}
set { _ProdSubTypes;= value; }
}
}
public class ProductSubTypes
{
private string _ProductSubTypeName;
public string ProductSubTypeName
{
get { return _ProductSubTypeName;
set { _ProductSubTypeName;}
}
private int _ParentID;
public int ParentID
{
get { return _ParentID;}
set { _ParentID;= value; }
}
private bool _IsAssigned;
public bool IsAssigned
{
get { return _IsAssigned; }
set
{
_IsAssigned = value;
if _ParentID;!= 0)
{
//updating data in database
//Calling and setting new collection value in property
//issue : updated collection sets in setter of MyTreeViewProperty but before calling getter
// it comes to IsAssigned getter so view doesnt get updated collection of MyTreeViewProperty
}
RaisePropertyChanged("IsAssigned");
}
}
}
}
View
<Page x:Class="ShiftManagerViews.Pages.ProductTreeSelection
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"
DataContext="{Binding ProductsTree, Source={StaticResource Locator}}"
mc:Ignorable="d" Width="870" Height="665"
>
<TreeView Margin="10,10,0,13" ItemsSource="{Binding MyTreeViewProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="800" Height="Auto" MinHeight="400" MaxHeight="800">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:AvailableProducts}"
ItemsSource="{Binding MyProductTypes}">
<WrapPanel>
<Image Width="20" Height="20" Source="/ShiftManagerViews;component/Images/12.bmp"/>
<Label Content="{Binding BrandName}" FontSize="14"/>
</WrapPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:ProductTypes}"
ItemsSource="{Binding ProdSubTypes}">
<WrapPanel>
<Image Width="18" Height="15" Source="/ShiftManagerViews;component/Images/12.bmp"/>
<Label Content="{Binding ProductTypeName}" FontSize="13"/>
</WrapPanel>
</HierarchicalDataTemplate>
<!-- the template for showing the Leaf node's properties-->
<DataTemplate DataType="{x:Type local:ProductSubTypes}">
<StackPanel>
<CheckBox IsChecked="{Binding IsAssigned, Mode=TwoWay}" Content="{Binding ProductSubTypeName}" Height="25">
</CheckBox>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
What about using a ListBox to display sub-items instead of a TreeView? You can style that so the items contain a CheckBox to show IsSelected instead of highlighting the item.
I'd suggest your user interface is wrong. If the user can only pick one then it would be better to swap these for radio buttons and add a "None of the above" option. That'll then give you the behaviour you want for free and your UI will be more intuitive.
EDIT: Since you say you can't add a "None" option and want to use a checkbox (even though I strongly disagree on checkboxes where a radio button is more appropriate - a common UI error)...
The technical problem you are probably facing is that an ObservableCollection only raises notification events if the collection itself changes. i.e. Only if items are added or removed. It does not raised events when items within the collection change, therefore the changing the status of the checkbox in the code will not raise the event for the UI binding to act on.
One solution to this to write a custom class that extends ObservableCollection that does provide this behaviour
From MSDN:
If you need to know if someone has changed a property of one of the
items within the collection, you'll need to ensure that the items in
the collection implement the INotifyPropertyChanged interface, and
you'll need to manually attach property changed event handlers for
those objects. No matter how you change properties of objects within
the collection, the collection's PropertyChanged event will not fire.
As a matter of fact, the ObservableCollection's PropertyChanged event
handler is protected—you can't even react to it unless you inherit
from the class and expose it yourself. You could, of course, handle
the PropertyChanged event for each item within the collection from
your inherited collection
I upvoted Rachel's answer, it is a common way in WPF to databind sets of radio buttons or check boxes. If you still want to go the tree view way, below code works. All view related code is in the view, so below code follows MVVM principles. If you are a MVVM purist you can put the code behind and a TreeView control in a user control if you do not want any code behind.
XAML:
<TreeView ItemsSource="{Binding Path=Drinks}">
<TreeView.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding .}" Checked="OnCheckBoxChecked" Unchecked="OnCheckBoxUnchecked" Loaded="OnCheckBoxLoaded" />
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code behind + VM:
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new VM();
}
private void OnCheckBoxChecked(object sender, System.Windows.RoutedEventArgs e)
{
foreach (CheckBox checkBox in _checkBoxes.Where(cb => cb != sender))
{
checkBox.IsChecked = false;
}
(DataContext as VM).CurrentDrink = (sender as CheckBox).Content.ToString();
}
private void OnCheckBoxUnchecked(object sender, System.Windows.RoutedEventArgs e)
{
(DataContext as VM).CurrentDrink = null;
}
private void OnCheckBoxLoaded(object sender, System.Windows.RoutedEventArgs e)
{
_checkBoxes.Add(sender as CheckBox);
}
private List<CheckBox> _checkBoxes = new List<CheckBox>();
}
public class VM
{
public List<string> Drinks
{
get
{
return new List<string>() { "Coffee", "Tea", "Juice" };
}
}
public string CurrentDrink { get; set; }
}
I did one trick but it didn't work that when i have already selected a
drink and then i select another one than i set the last selected value
in the observable collection to false but it doesn't affect on view
and selected check boxes remains selected although in collection only
one option's value is true.
Make sure that your child objects (AvailableProducts
and SubProductTypes) also implement INotifyPropertyChanged, this will make sure that the UI receives changes when modify the object.
Once all of you objects update the UI properly you will be able to layer in, and test, whatever custom business logic you need.
So if you have a product type that can only have one sub chosen, you could add a property on ProductType called OnlyAllowOneChild. Whenever, a child object raises a IsAssigned changed event, the parent can set false all other children. This of course requires you to have the parent either register for the children's PropertyChangedEvent, or got grab an EventAggregator (MVVMLight Messenger, or PRISM EvenAggregator) and create a messaging system.
Finally i am succeeded to solve my problem.
on Is Assigned property i am updating my database values and calling a method in view using MVVM Light messaging and passing currently selected leaf's parent id in it as a parameter...
Added a property in class Product Types to expand the parent node of the last selected leaf..
In view's method i am refreshing data context's source and passing currently selected leaf's parent id tO the VM to set its Is Expanded property value to true...
By this my view is working perfectly as same as i want...
If any body have solution better than this than I'll be happy to know.
I don't know what I'm doing wrong here. I have a ListBox whose DataContext and ItemsSource are set, but there is nothing in the ListBox when I run my app. When debugging, the first line of my method for getting items for the ListBox never gets hit. Here's what I have:
// Constructor in UserControl
public TemplateList()
{
_templates = new Templates();
InitializeComponent();
DataContext = this;
}
// ItemsSource of ListBox
public List<Template> GetTemplates()
{
if (!tryReadTemplatesIfNecessary(ref _templates))
{
return new List<Template>
{
// Template with Name property set:
new Template("No saved templates", null)
};
}
return _templates.ToList();
}
Here's my XAML:
<ListBox ItemsSource="{Binding Path=GetTemplates}" Grid.Row="1" Grid.Column="1"
Width="400" Height="300" DisplayMemberPath="Name"
SelectedValuePath="Name"/>
On an instance of the Template class, there's a Name property that is just a string. All I want is to display a list of template names. The user won't change any data in a Template, the ListBox just needs to be read-only.
A Template also has a Data property that I will later display in this ListBox, so I don't want to make GetTemplates return just a list of strings--it needs to return some collection of Template objects.
You can't bind to a method. Make it a property and it should work.
Its better though to set the List as DataContext, or create a ViewModel that holds the list. Thay way, you will have more control over the instances your Listbox will bind to.
Hope this helps!
You're attempting to call a method in your binding when you should be using a property. Change it to a property and you should be good to go.
public List<Template> MyTemplates {get; private set;}
public TemplateList()
{
InitializeComponent();
SetTemplates();
DataContext = this;
}
// ItemsSource of ListBox
public void SetTemplates()
{
// do stuff to set up the MyTemplates proeprty
MyTemplates = something.ToList();
}
Xaml:
<ListBox ItemsSource="{Binding Path=MyTemplates}" Grid.Row="1" Grid.Column="1"
Width="400" Height="300" DisplayMemberPath="Name"
SelectedValuePath="Name"/>