Setting WPF Delay from code in MVVM - c#

I am working on WPF application (.NET 4.5), and using Caliburn.Micro. I am generating a list of questions and answers in ListBox answers can be of 3 types (RadioButtons, DropDowns and Textboxes). When question is being answered, property triggers change in ViewModel and next question is being added.
Problem I ran into is when symbol is being added to Textbox it immediately fires property change.
public string TextValue
{
get { return _textValue; }
set
{
_textValue = value;
NotifyOfPropertyChange(() => TextValue);
}
}
Normally (for non dynamically generated controls) I could delay it by using 'new'
<TextBlock Text="{Binding TextValue, Delay=500}"/>
but since I am generating these questions I am not sure on how to proceed with this.
Is there a way to set Delay to generated control from code behind?
UPDATE:
this is how XAML looks like. List is being populated with questions on runtime (from DB), and questions do change based on previous answers, so there is no way of setting anything in XAML.
<UserControl x:Class="Corp.Conveyancing.Desktop.Views.Probate.PifWFlowQuestionsView"
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:probate="clr-namespace:Corp.Conveyancing.Desktop.Views.Probate"
mc:Ignorable="d" >
<Grid Margin="10" Width="600" Height="400" >
<ListBox x:Name="QuestionItems" Grid.Row="0" BorderThickness="0" HorizontalContentAlignment="Stretch" ScrollViewer.CanContentScroll="true" ScrollViewer.VerticalScrollBarVisibility="Visible" Height="380" Width="580" probate:ListBoxExtenders.AutoScrollToEnd="True" >
<ListBox.ItemContainerStyle >
<Style TargetType="ListBoxItem">
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</UserControl>
UPDATE2:
I have BindableCollection<QuestionItemViewModel> which is property in PifWFlowQuestionsViewModel to which I am adding only first question and then based on answer I am adding more questions,a nd based on those adding more and so on.
public class PifWFlowQuestionsViewModel : PropertyChangedBase
{
private BindableCollection<QuestionItemViewModel> _questionItems =
new BindableCollection<QuestionItemViewModel>();
public BindableCollection<QuestionItemViewModel> QuestionItems
{
get { return _questionItems; }
set
{
_questionItems = value;
NotifyOfPropertyChange(() => QuestionItems);
}
}
}

This is how I ended up solving it, might be not THE solution, but it was quick and easy.
I realize that it does not answer to the title of the question, however it does provide solution to the problem above, and I am sure someone will find this useful.
public class QuestionTextViewModel : QuestionItemViewModel
{
private Timer Timer { get; set; }
public QuestionTextViewModel(IEventAggregator eventAggregator, TransactionDetail transactionDetail)
: base(transactionDetail)
{
_eventAggregator = eventAggregator;
TransactionDetailId = transactionDetail.TransactionDetailId;
this.Timer = new Timer(1500) {AutoReset = false};
this.Timer.Elapsed += TextValueTimer_Elapsed;
}
public string TextValue
{
get { return _textValue; }
set
{
_textValue = value;
this.Timer.Stop();
this.Timer.Start();
}
}
private void TextValueTimer_Elapsed(object sender, ElapsedEventArgs e)
{
NotifyOfPropertyChange(() => TextValue);
}
}

You should be using an ItemTemplateSelector on the ListBox and basing what type of DataTemplate is used for the QuestionItemViewModel based on the returned properties for the new item.
It would definitely be possible to do the control generation in XAML then as you're specifying the DataTemplate content.

Related

ItemsControl displaying class name

This app is displaying the class name of a collection instead of a text-box as desired. I've read other issues with this, but cannot figure out what I'm missing. I have a datacontext, I'm bound to the collection as an itemsource, and I've added a single item. All I want is to bind the collection 'Boxes' in my view model 'DrawBoxViewModel' to an item source, and have it display a single item as a text box. All help is appreciated.
First my XAML:
<Page
x:Class="BoxMaker2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BoxMaker2"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:BoxMaker2.ViewModels"
mc:Ignorable="d">
<Page.Resources>
<vm:DrawBoxViewModel x:Key="DrawBoxViewModel"/>
</Page.Resources>
<Canvas DataContext="{Binding Source={StaticResource DrawBoxViewModel}}">
<ItemsControl ItemsSource="{Binding Boxes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="350" Height="600" Background="AliceBlue"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate x:DataType="vm:Box" x:Key="test">
<VariableSizedWrapGrid>
<TextBox Background="White"
Text="{x:Bind Data}"
Width="100"
Height="100"/>
<VariableSizedWrapGrid.RenderTransform>
<TranslateTransform X="{Binding LeftCanvas}" Y="{Binding TopCanvas}"/>
</VariableSizedWrapGrid.RenderTransform>
</VariableSizedWrapGrid>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Canvas>
And now my viewmodel:
namespace BoxMaker2.ViewModels
{
public class DrawBoxViewModel
{
#region fields
private ObservableCollection<Box> _boxes;
#endregion
#region properties
public ObservableCollection<Box> Boxes { get { return this._boxes; } }
#endregion
#region constructors
public DrawBoxViewModel()
{
this._boxes = new ObservableCollection<Box>();
_boxes.Add(new Box() { Data = "hello!", LeftCanvas = 200, TopCanvas = 200 });
}
#endregion
}
public class Box : INotifyPropertyChanged
{
private int _generation;
public int Generation
{
get { return _generation; }
set { _generation = value; OnPropertyChanged("Generation"); }
}
private int _childNo;
public int ChildNo
{
get { return _childNo; }
set { _childNo = value; OnPropertyChanged("ChildNo"); }
}
private Box _parentBox;
public Box ParentBox
{
get { return _parentBox; }
set { _parentBox = value; OnPropertyChanged("ParentBox"); }
}
private List<Box> _childrenBox;
public List<Box> ChildrenBox
{
get { return _childrenBox; }
set { _childrenBox = value; OnPropertyChanged("ChildrenBox"); }
}
private string _data;
public string Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged("Data");
}
}
private double _topCanvas;
public double TopCanvas
{
get { return _topCanvas; }
set
{
_topCanvas = value;
OnPropertyChanged("TopCanvas");
}
}
private double _leftCanvas;
public double LeftCanvas
{
get { return _leftCanvas; }
set
{
_leftCanvas = value;
OnPropertyChanged("LeftCanvas");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I am not exactly sure what you are trying to achieve but here's a few issues I have found in your code.
You should assign your VM to the DataContext of the Page
directly.
<Page.DataContext>
<vm:DrawBoxViewModel />
</Page.DataContext>
After doing so, you can now remove DataContext="{Binding
Source={StaticResource DrawBoxViewModel}}" from your Canvas.
Replace <ItemsControl.Resource> with
<ItemsControl.ItemTemplate> and remove x:Key="test", assuming you want to show multiple
TextBoxes on the UI. The DataTemplate within the Resource you
defined won't do anything until you reference it by its key. I don't
think you really want that here though.
You should use x:Bind for your X & Y binding
<TranslateTransform X="{x:Bind LeftCanvas}"
Y="{x:Bind TopCanvas}" />
Your Boxes collection can be simplified as following
#region properties
public ObservableCollection<Box> Boxes { get; } = new ObservableCollection<Box>();
#endregion
#region constructors
public DrawBoxViewModel()
{
Boxes.Add(new Box() { Data = "hello!", LeftCanvas = 0, TopCanvas = 200 });
}
#endregion
Hope this helps!
Your Items control doesn't know which data template to use. Currently your view model has a template associated to it via the x:DataType="vm:Box" which is defined as a resource in the items control.
The problem is that Universal Windows Platform doesn't recognize templates associated to data types. So even though there is a template, the control doesn't know how to find it when it is rendering the collection of view models.
Automatic resolving of templates based on bound types was a function of WPF which is not available in UWP.
What that means is that in WPF you could associate a data template to a class/object via the x:DataType="Object Type" attribute of the data template (which is what you did). When the collection is bound, the rendering engine would auto-magically match the the individual items in the collection to their respective templates.
This was very powerful because if your collection had many different types of boxes for example (or things inheriting from DrawBoxViewModel) you could render each item type differently by simply defining a template. Well this is no more. Microsoft destroyed that feature in UWP.
So long story short - move the template to the page resource collection. Give it a key such as:
<Page.Resources>
<vm:DrawBoxViewModel x:Key="DrawBoxViewModel"/>
<DataTemplate x:Key="test">
<VariableSizedWrapGrid>
<TextBox Background="White"
Text="{x:Bind Data}"
Width="100"
Height="100"/>
<VariableSizedWrapGrid.RenderTransform>
<TranslateTransform X="{Binding LeftCanvas}" Y="{Binding TopCanvas}"/>
</VariableSizedWrapGrid.RenderTransform>
</VariableSizedWrapGrid>
</DataTemplate>
</Page.Resources>
Reference the template in your items control as follows:
<ItemsControl ItemsSource="{Binding Boxes} ItemTemplate={StaticResource test} ">

Trying to get add something to an Listview when I press a button

I'm working to figure out data binding and item sources with WPF and C#, and I'm missing something when it comes to making the proper binding connections. The result is a runtime error:
Cannot find source for binding with reference 'ElementName=SettingsWindow'. BindingExpression:Path=teams; DataItem=null; target element is 'ListView' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
The code itself is pretty straightforward (I think):
SettingsWindow.xaml:
<Window x:Class="Bridge.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Settings" Height="480" Width="600">
...
<Button Grid.Column="0" Grid.Row="0" Content="Add Team" Click="ClickAddTeam"/>
<ListView ItemsSource="{Binding}" Grid.Column="0" Grid.Row="1">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Foreground" Value="{Binding team.color}" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
...
</Window>
SettingsWindow.xaml.cs:
public partial class SettingsWindow : Window
{
public TeamList teams { get; set; }
public SettingsWindow()
{
teams = TeamManager.Instance().teamList; // persists in a different class
this.DataContext = this.teams;
InitializeComponent();
}
private void ClickAddTeam(object sender, RoutedEventArgs e)
{
TeamManager manager = TeamManager.Instance();
Team toAdd = manager.GetSampleTeam(teams.Count);
Console.WriteLine(toAdd.ToString());
teams.Add(toAdd);
if(teams.Count == manager.sampleTeams.Count)
(sender as Button).IsEnabled = false;
}
}
Team.cs:
namespace DataTypes
{
public class Team
{
public string name;
public Brush color;
public Team(string name, Brush color)
{
this.name = name;
this.color = color;
}
}
}
Now working:
The WriteLine in ClickAddTeam is printing the right data, so I know it's retrieving the Team object correctly and that the MyTeamList teams object is getting stuff added to it. The button is also being disabled after the appropriate number of clicks. The ListBox, however, stays empty the entire time.
Next step:
I'm trying to get the strings in the ListBox to be team.name rather than "DataTypes.Team", and for the text's foreground to be the team's color. How do I grab a specific property of the bound element?
Any help would be greatly appreciated!
Binding only works with public properties. Try to declare teams as property instead of field/member:
public MyTeamList teams { get; set; }
// OP note: This part below wouldn't compile, giving me a member names cannot be the same as their enclosing type error.
and because you're binding by ElementName, you need to name you Window properly :
<Window x:Class="Bridge.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="SettingsWindow"
Title="Settings" Height="480" Width="600">
[OP Note]
Just changing that line to ItemsSource="{Binding}" works for displaying stuff, but every item displays as "DataType.Team" instead of a proper string. That's the next thing to figure out.
[/OP Note]
It is supposed to be one question per question
You need to set the DataContext
this.DataContext = this;
ItemsSource="{Binding Path=TeamList}" DisplayMemberPath=Name
And name needs to be a public property (get)

How to Get a Reference to a ViewModel

All, I have a custom DataGridView control which overrides the DataGidView's OnItemsSourceChanged event. Inside this event I need to get a reference to a data set in the relevant ViewModel. Code is
public class ResourceDataGrid : DataGrid
{
protected override void OnItemsSourceChanged(
System.Collections.IEnumerable oldValue,
System.Collections.IEnumerable newValue)
{
if (Equals(newValue, oldValue))
return;
base.OnItemsSourceChanged(oldValue, newValue);
ResourceCore.ResourceManager manager = ResourceCore.ResourceManager.Instance();
ResourceDataViewModel resourceDataViewModel = ?? // How do I get my ResourceDataViewModel
List<string> l = manger.GetDataFor(resourceDataViewModel);
...
}
}
On the marked line I want to know how to get a reference to ResourceDataViewModel resourceDataViewModel. The reson is that i have multiple tabs each tab contains a data grid and ascociated ViewModel, the ViewModel holds some data that I need to retrieve [via the ResourceManager] (or is there another, better way?).
The question is, from the above event, how can I get the ascociated ResourceDataViewModel?
Thanks for your time.
Get the DataContext and cast it to the view-model type:
var viewModel = this.DataContext as ResourceDataViewModel
Put a static reference to it on your app, when the VM is created place its reference on the static and access it as needed.
You ask if there is a better way... In my experience if you find yourself subclassing a UI element in WPF there ususally is.
You can get away from embedding business logic (the choice of which data to display in the grid), by databinding your entire tab control to a view model.
To demonstrate - here is a very simple example. This is my XAML for the window hosting the tab control:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TabControl ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding TabName}"></Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid>
<DataGrid ItemsSource="{Binding TabData}"></DataGrid>
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
The data context of my window is a TabsViewModel (I am using the NotificationObject that can be found in the PRISM NuGet Package):
public class TabsViewModel: NotificationObject
{
public TabsViewModel()
{
Tabs = new[]
{
new TabViewModel("TAB1", "Data 1 Tab 1", "Data 2 Tab1"),
new TabViewModel("TAB2", "Data 1 Tab 2", "Data 2 Tab2"),
};
}
private TabViewModel _selectedTab;
public TabViewModel SelectedTab
{
get { return _selectedTab; }
set
{
if (Equals(value, _selectedTab)) return;
_selectedTab = value;
RaisePropertyChanged(() => SelectedTab);
}
}
public IEnumerable<TabViewModel> Tabs { get; set; }
}
public class TabViewModel
{
public TabViewModel(string tabName, params string[] data)
{
TabName = tabName;
TabData = data.Select(d => new RowData(){Property1 = d}).ToArray();
}
public string TabName { get; set; }
public RowData[] TabData { get; set; }
}
public class RowData
{
public string Property1 { get; set; }
}
This is obviously an over simplified case, but it means that if there is any business logic about precisely what data to show in each tab, this can reside in one of the view models, as opposed to the code behind. This gives you all the 'separation of concerns' benefits that MVVM is designed to encourage...

What is the best way to create a menu bar that follows the MVVM paradigm?

This is my first question on SO, so Hello.
I'm pretty new to WPF and so I decided to try and create a GUI for a library I created in the past. I wanted to create a menu strip/line/bar for the application and this is pretty simple to do with the Menu control. However, I saw mentions of MVVM and looked it up, and decided I wanted to create the menu dynamically from a collection.
This is all well and good, I found a lot of material regarding the subject and ended up with the following ViewModel for my MenuItems:
class MenuItemViewModel
{
public string Text { get; set; }
public ICommand Command { get; set; }
public ObservableCollection<MenuItemViewModel> Children
{
get
{
return new ObservableCollection<MenuItemViewModel>(children);
}
}
private List<MenuItemViewModel> children;
public MenuItemViewModel()
{
children = new List<MenuItemViewModel>();
}
}
And the View (with extras):
<Window x:Class="GUI.MainWindow"
xmlns=""
"
"
Title="HTX-Formler" Height="350" Width="525"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Path=Text}"/>
<Setter Property="Command" Value="{Binding Path=Command}"/>
</Style>
<HierarchicalDataTemplate
DataType="{x:Type vm:MenuItemViewModel}"
ItemsSource="{Binding Path=Children}">
</HierarchicalDataTemplate>
</Window.Resources>
<DockPanel KeyboardNavigation.TabNavigation="None">
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=Children}" />
</DockPanel>
As far as I can tell, the binding is fine, but whatever I do, I cannot get the menu to display anything what so ever.
I have a feeling it is because I am using the wrong ItemsSource, so I tried doing the following:
class MainWindowViewModel
{
#region Fields
ReadOnlyCollection<MenuItemViewModel> _menuItems;
#endregion
#region Menu
public ReadOnlyCollection<MenuItemViewModel> Children
{
get
{
if (_menuItems == null)
{
List<MenuItemViewModel> menuItems = this.PopulateMenu();
_menuItems = new ReadOnlyCollection<MenuItemViewModel>(menuItems);
}
return _menuItems;
}
}
List<MenuItemViewModel> PopulateMenu()
{
return new List<MenuItemViewModel>
{
new MenuItemViewModel()
};
}
#endregion
}
But to no avail. I realise that the collection has the same name as the Children property, but changing the name of the col. has no effect.
Summary
What is the best way to create the menu bar in this way?
What is the best way to populate the menu collection?
A great deal of inspiration were taking from the following sources:
Yes, some of the links are to SO.
No, I do not think this is a duplicate question, as I think that the aforementioned sources are missing critical parts which are inherent to my understanding. If this is somehow a duplicate, I would appreciate it if someone would point out the information i missed.
I apologize for the triviality of this and knowing my brain, the answer is probably really simple, but I am literally at my wit's end here, so any and all answers will be appreciated.
Simplest solution: set the DataContext in the window's constructor:
public class MainWindow: Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
You can set the DataContext of the entire Window in XAML by putting this in your Window definiton, e.g. below your </Window.Resources>:
<Window.DataContext>
<vm:MenuItemViewModel/>
</Window.DataContext>

How to group checkboxes in treeview wpf mvvm when selection range is [0,1]

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.

Categories