I have 2 combobox, which include menus name and its details like (Pie-apple, chocolate), (Juice- apple, orange).
So if I select 'Pie' in the first combobox, second one should have its details-apple and chocolate.
Xaml code is below:
<DockPanel Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" VerticalAlignment="Center" Margin="5">
<TextBlock Text="Menu : " HorizontalAlignment="Left" Height="32"/>
<ComboBox ItemsSource="{Binding LstMenu}" SelectedValue="{Binding SelectedMenu}" HorizontalAlignment="Left" Height="32"/>
<TextBlock Text="Detail: " HorizontalAlignment="Left" Height="32"/>
<ComboBox ItemsSource="{Binding LstDetail}" SelectedValue="{Binding SelectedDetail}" HorizontalAlignment="Left" Height="32" SelectionChanged="combobox_changed"/>
</DockPanel>
and viewmodel is below:
private List<string> lstMenu;
public List<string> LstMenu {
get { return lstMenu; }
set {
lstMenu = value;
RaisePropertyChanged("LstMenu");
}
private string selectedMenu;
public string SelectedMenu {
get { return selectedMenu; }
set {
selectedMenu= value;
RaisePropertyChanged("SelectedMenu");
LstDetails = new ObservableCollection<string>( //get its details list from DB );
}
private ObservableCollection<string> lstDetail;
public ObservableCollection<string> LstDetail {
get { return lstDetail; }
set {
lstDetail= value;
RaisePropertyChanged("LstDetail");
if (lstDetail != null && lstDetail.Count > 0) {
SelectedDetail = lstDetail.FirstOrDefault();
}
}
private string selectedDetail;
public string SelectedDetail {
get { return selectedDetail; }
set {
selectedDetail= value;
RaisePropertyChanged("SelectedDetail");
}
The problem is, if I selected Pie-apple and then changed the first combobox to Juice menu, Juice-apple is automatically selected but RaisePropertyChanged("SelectedDetail"); does not work.
I know that's because the selected two strings apple are same...
However I need to call the event SelectionChanged="combobox_changed" since it contains UI reload event.
Moreover, if I added IsAsync=True into the LstDetail combobox, it definitely works but SelectionChanged="combobox_changed" is called twice. I don't know why.
what should I do?
It's hard to know exactly what the architecture of your program is, without a good Minimal, Reproducible Example. But, from what you've posted, it seems you are trying to do too much of the work, and not letting WPF do its fair share. In particular, you seem to have just one "view model" type, and in that object you attempt to completely define everything that the user sees, and the state of that.
As has been noted in the comments, this has led to a situation where there are pieces of state that relate to each other, but where you haven't done enough work to make sure they stay synchronized with each other. You've delegated some of the work to WPF, but it doesn't have enough context to let you know when something important has changed, and so your UI winds up in a inconsistent state.
It would in fact be possible to fix your program as it stands now, by forcing the SelectedDetail property to refresh itself if the menu combo box changes. But a) I can't tell you exactly how to do that, because you've omitted all of the details that relate to the actual management of that property (such as the combobox_changed() event handler), and b) that's really just too much work anyway.
The first thing you need to get on board with, when writing WPF code, is to put as much of your program specification into declarations, and not procedures. WPF's binding mechanisms do a great job of automatically keeping view model data structures in sync with the actual UI. This means that you can view your program as two completely independent entities: the user interface itself, and the "business logic", i.e. the things your program actually has to do. The "view model" part mediates between these two elements. In the simplest WPF programs, the business logic itself can be entirely encapsulated in the view model data structures; in more complex applications, the view models focus on mapping between the UI ("view") and the business logic ("model").
This has an important implication: if you find yourself writing code that is directly interacting with the view element of your program — i.e. either responding to the UI or modifying it — that code had better be strictly specific to the view. Another way to look at that is, such code should be reusable with any other type of business logic, just as all of the built-in XAML stuff is completely reusable and not at all specific to your business logic.
Conversely, if that code you're writing is fiddling with the view model data structures directly or, even worse, is actually part of the view model data structures, you've gone off into the weeds. This should never happen.
You can use these two metrics to constantly evaluate as you go along whether you're designing the code correctly, and to help guide that design before you actually write the code.
Okay, with that little bit of indoctrination out of the way, here's how I would implement your stated goal:
You need some view models. Not just one, because you have a hierarchy of business logic objects, and the view models need to reflect that. Working from the bottom up:
You need a view model that can represent the detail to be displayed. For example:
class DetailViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name
{
get => _name;
set => _UpdateField(ref _name, value);
}
private string _description;
public string Description
{
get => _description;
set => _UpdateField(ref _description, value);
}
private decimal _price;
public decimal Price
{
get => _price;
set => _UpdateField(ref _price, value);
}
}
Notes:
The above relies on a base class NotifyPropertyChangedBase I use for all view models, which provides a convenient mechanism to implement observable properties. Code for that is provided below.
The above is strictly a simple data container. For this example, that's all that's needed, because all the example is concerning itself with is how to react to UI input, and WPF is great at managing that already, as long as it has a place to keep everything. A more interesting WPF program would have procedural aspects in the view model for providing commands that operate on the data beyond what the XAML is capable of defining.
Okay, so with a details data structure, we also need a place to keep a list of these objects for each type of menu in your program:
class MenuViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name
{
get => _name;
set => _UpdateField(ref _name, value);
}
private List<DetailViewModel> _menuItems = new List<DetailViewModel>();
public List<DetailViewModel> MenuItems
{
get => _menuItems;
set => _UpdateField(ref _menuItems, value);
}
private DetailViewModel _selectedItem;
public DetailViewModel SelectedItem
{
get => _selectedItem;
set => _UpdateField(ref _selectedItem, value);
}
}
You'll note that the above two view model data structures have a Name property. This is used to display to the user the name of the item they will be selecting.
The new aspect in this view model is the list of menu item objects, and then a property that keeps track of the currently selected menu item object. This is critical with respect to your question: in your implementation, the only thing you know about the currently selected item is its name. But when the same name appears on two different menus, you've got no way to distinguish the two. The only way out of that dilemma, given the design you chose, is to always refresh the details explicitly when the selected menu changes.
But here, we tie the selected item to the menu itself. This gives us two nice results:
When the menu changes, then whatever's bound to the selected item property will change as well, implicitly updating the displayed details, because WPF's binding engine understands the relationships of the properties involved. In particular, the details aren't just some random string, but rather a specific object that was retrieved from a different specific object. If that latter specific object is no longer the context for the binding (i.e. the user picks a new menu), then WPF knows that the former specific object needs to be re-evaluated.
By default, the user's selection for a given menu is remembered, because each menu has its own SelectedItem property! When the user selects an item from a menu, then selects a different menu, then after they are done with that second menu and go back to the first, the first will still have their previous selection from that menu. Now, this may or may not be the desired behavior. If not, it's reasonably easy in the view model to reset the selected item when the menu changes. But it's usually easier to suppress functionality than to create it, so having the default behavior provide that added functionality is nice.
Finally, of course, we need a place to keep track of the currently selected menu:
class MainViewModel : NotifyPropertyChangedBase
{
private List<MenuViewModel> _menus = new List<MenuViewModel>();
public List<MenuViewModel> Menus
{
get => _menus;
set => _UpdateField(ref _menus, value);
}
private MenuViewModel _selectedMenu;
public MenuViewModel SelectedMenu
{
get => _selectedMenu;
set => _UpdateField(ref _selectedMenu, value);
}
}
Just like with the menu object, this one has both a list of items (menus, in this case) and a property that keeps track of which specific item is selected.
Now that the view model data structures have been correctly designed to reflect the hierarchy of user selection in our user interface, it's a very simple matter to declare the user interface to work with those data structures:
<Window x:Class="TestSO58167153WpfTwoLevelDetail.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:TestSO58167153WpfTwoLevelDetail"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Key="comboBoxNameTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:DetailViewModel}">
<StackPanel>
<TextBlock Text="{Binding Description}"/>
<TextBlock Text="{Binding Price, StringFormat={}Price: ${0:0.00}}"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" Grid.Column="0" Width="100"
ItemsSource="{Binding Menus}" SelectedItem="{Binding SelectedMenu}"
ItemTemplate="{StaticResource comboBoxNameTemplate}"/>
<ComboBox Grid.Row="0" Grid.Column="1" Width="100" HorizontalAlignment="Left" Margin="10,0"
ItemsSource="{Binding SelectedMenu.MenuItems}" SelectedItem="{Binding SelectedMenu.SelectedItem}"
ItemTemplate="{StaticResource comboBoxNameTemplate}"/>
<ContentPresenter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
Content="{Binding SelectedMenu.SelectedItem}"/>
</Grid>
<Window.DataContext>
<l:MainViewModel>
<l:MainViewModel.Menus>
<l:MenuViewModel Name="Pies">
<l:MenuViewModel.MenuItems>
<l:DetailViewModel Name="Apple" Description="Apple Pie with Pastry Crust" Price="10.50"/>
<l:DetailViewModel Name="Grasshopper" Description="Mint Pie with Oreo Crust" Price="17.95"/>
</l:MenuViewModel.MenuItems>
</l:MenuViewModel>
<l:MenuViewModel Name="Juice">
<l:MenuViewModel.MenuItems>
<l:DetailViewModel Name="Apple" Description="Refreshing Apple Juice" Price="3.70"/>
<l:DetailViewModel Name="Mango" Description="Sweet Mango Juice" Price="4.75"/>
</l:MenuViewModel.MenuItems>
</l:MenuViewModel>
</l:MainViewModel.Menus>
</l:MainViewModel>
</Window.DataContext>
</Window>
There are two main components to the above:
Data templates. These tell WPF how to map the view model data structure to elements in the UI. There are two here: a general-purpose one that just always shows, in a TextBlock element, the Name property of any view model data type; and a template that is specific to the DetailsViewModel object, and which displays just the values we're interested in as details.
The UI itself. This is super-simple: two ComboBox elements, providing the drop-down interface to select both a menu and an item on that menu; one ContentPresenter, a control whose main job is just to provide a place to render a data template for a given object; and a Grid object to organize it all. The ComboBox controls explicitly opt in to the data template that displays the item's Name property value, while the ContentPresenter infers the correct data template from the type of view model being used (but it also allows the template to be set explicitly, if you so desire).
The only other thing up there is the DataContext for the window itself, the content of which I've declared in the XAML here just because it's convenient for the purpose of the sample. In your real-world program, which appears to retrieve data from a database, you'd probably have the top-level view model handle populating itself based on that.
(Speaking of the data context: in the above, all binding paths are relative to the top-level view model. For the purpose of the sample, this is more convenient, but you of course have complete control over the data context for any element in the UI. An alternative way to implement this would be to set the DataContext properties for the controls lower in the dependency hierarchy, so that you don't have to repeat the top-level view model's property names in the binding paths.)
And that's all there is to it. You can compile and run the above code, and it will do just what you're asking for your code to do.
Minor notes:
All of the selections start out blank; you can of course initialize them to non-null values if you want, but doing so in the sample above would just add more code for no useful benefit, at least for the purpose of the sample.)
The view models here all use List<T> for their collections. This is fine for the example, because these collections never change. But as you likely already know, real-world WPF programs usually use ObservableCollection<T>, because generally they are including features that allow for those collections to be modified while the program runs. ObservableCollection<T> implements INotifyCollectionChanged, which in turn allows WPF to keep the UI in sync with the bound data. Feel free to replace List<T> with ObservableCollection<T> here or in any other similar scenario.
As promised, here's the code for the NotifyPropertyChangedBase class. There are lots of different ways to implement a base class like this, and in fact I have a different version with a couple more features that I typically use. But this one works well for a basic WPF example (indeed, for many even this one is too "feature-rich" :) ):
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));
}
}
Just hit a snag in my MVVM application that's made me wonder if I'm doing MVVM "right", or if I've missed a trick somewhere. Consider a situation like this on the viewmodel:
private _bookings ObservableCollection<Booking>;
public ObservableCollection<Booking> Bookings
{
get { return _bookings; }
set
{
_bookings = value;
OnPropertyChanged("Bookings");
}
}
public int Requested
{
get
{
return (int)Bookings.Sum(g => g.Requested);
}
}
public int Available
{
get
{
return (int)Bookings.Sum(g => g.Volume);
}
}
And then in a datagrid on the view:
<DataGrid DataContext="{Binding Bookings, Mode=TwoWay}" ItemsSource="{Binding SourceCollection}">
<!-- various datagrid related gubbins -->
</DataGrid>
<Label BorderThickness="1" Content="{Binding Requested}"></Label>
<Label BorderThickness="1" Content="{Binding Available}"></Label>
Now, whenever something in the ViewModel changes the contents of the Bookings collection, the contents of my grid will auto-update which is great.
But the Active and Requested properties which are calculated on the contents of that collection don't auto update, and can't because they're read-only. Even if I made each one two way and raised a changed event for them both (which I'm reluctant to do, because in the real-world version there are a lot of these dependent properties), the code chokes on execution because you can't make a two-way binding to a read-only property.
Is there any straightforward way round this problem, ideally to cascade changes to dependent properties and have them auto-update in the view?
Like #dellywheel mentioned in his answer that you need to cascade property changes if you want controls bound to these properties to get change notification.
However, if you are dealing with small class and can afford property changes, you can raise PropertyChanged event with empty string so all properties in class will pass on property change notification to UI. That ways you won't have to raise individual property change events for each properties.
OnPropertyChanged("");
You need to raise the OnPropertyChanged after Bookings collection changes (or wherever you need to in your code).
private _bookings ObservableCollection<Booking>;
public ObservableCollection<Booking> Bookings
{
get { return _bookings; }
set
{
_bookings = value;
OnPropertyChanged("Bookings");
OnPropertyChanged("Requested");
OnPropertyChanged("Available");
}
}
I'm trying to bind the IsEnabled property of a ToggleButton with no success.
once the NotifyOfPropertyChange is fired, I'm getting the following exception:
Value does not fall within the expected range.
Using a simple Button, the above configurations works as expected.
I wonder if there any workaround for that one?
Thanks
UPDATE:
well it took me a while to pinpoint the problem, but finally managed to understand the behavior:
I've created a simple tester where I use a button to enable/disable a ToggleButton.
when the ToggleButton control does not contain anything, all works properly; however, after adding sub controls to it (in our case I just added a StackPanel) an exception is raised:
Value does not fall within the expected range - right after NotifyOfPropertyChange() is called.
Here is the problematic view I'm using:
<StackPanel>
<ToggleButton x:Name="SayHello" Grid.Column="1" IsEnabled="{Binding HasValue}" Height="190">
<StackPanel x:Name="sp"> </StackPanel>
</ToggleButton>
<Button x:Name="Click"></Button>
</StackPanel>
The ViewModel:
private bool _hasvalue;
public bool HasValue
{
get { return _hasvalue; }
set
{
_hasvalue = value;
NotifyOfPropertyChange(() => HasValue);
}
}
public void Click()
{
HasValue = !HasValue;
}
Any way to workaround that one? - the platforms is WP8.
I couldn't replicate the error from the example above, is there additional information in your ViewModel?
you should also be able to get the effect you want (although I'd still be interested to see the root cause of your error), by using the Caliburn.Micro conventions. Is x:Name=sp causing anything to be bound?
If you have a method SayHello, with a UI element bound to the method via a convention: x:Name="SayHello"
You can create a bool property on your ViewModel called CanSayHello, which Caliburn.Micro will use to Enable/Disable the control; although you will have to call NotifyPropertyChanged when that property changes (so the UI is aware and can update the control).
E.g.
<!-- Your existing Control, Note `IsEnabled` is not bound -->
<ToggleButton x:Name="SayHello" Height="40">
// On your ViewModel
public bool CanSayHello
{
get
{
return HasValue;
}
}
public void Click()
{
HasValue = !HasValue;
NotifyOfPropertyChange(() => CanSayHello);
}
Some additional info.
I have a textbox which is bound to a property ItemID like so.
private string _itemID;
public string ItemID {
get { return _itemID; }
set {
_itemID = value;
}
}
The XAML of the text box is as follows:
<TextBox Text="{Binding Path=ItemID, Mode=TwoWay}" Name="txtItemID" />
The problem is, the value of ItemID does not update immediately as I type,
causing the Add button to be disabled (command), until I lose focus of the text box by pressing the tab key.
Yes, by default, the property would be updated only on lost focus. This is to improve performance by avoiding the update of bound property on every keystroke. You should use UpdateSourceTrigger=PropertyChanged.
Try this:
<TextBox
Text="{Binding Path=ItemID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Name="txtItemID" />
You should also implement the INotifyPropertyChanged interface for your ViewModel. Else, if the property is changed in the ViewModel, the UI would not get to know about it. Hence it would not be updated. This might help in implementation.
You need to fire the OnPropertyChanged event in the setter, otherwise the framework has no way of knowing that you edited the property.
Here are some examples:
Implementing NotifyPropertyChanged without magic strings
Notify PropertyChanged after object update
This question deals with a dinky little Winforms GUI. Let it be known that I have basic knowledge of data bindings and INotifyPropertyChanged and use both in my ViewModels in WPF. But I don't know Winforms. This is for a school assignment.
So I have a class that has a DisplayName property. I also have a ListBox whose Items are a sequence of instances of my class. I have pointed myListBox.DisplayMember = "DisplayName"; After changing a value in an instance of my class that will cause the DisplayName property to return a different value, how do I tell my ListBox to pull the DisplayName property again to refresh its value?
I needed to do the same thing but with a combobox. The workaround I found is to clear and reset the DisplayMember property.
This worked:
myComboBox.DisplayMember = null;
myComboBox.DisplayMember = "DisplayName";
It's important to note that this is perhaps not the best solution as it will cause multiple SelectedValueChanged events but the end result is successful.
Doing it this way probably requires re-binding the listbox, loosing selectedIndex etc.
One workaround is to forget about the DisplayMember property and handle the Format event of the ListBox instead. Something like (from memory) :
// untested
e.Value = (e.Item as MyClass).DisplayValue;
I know this was ages ago but I had similar problem and could not find satisfying solution and finally solved with this single line at the end after updating the values:
bindingsource.EndEdit();
Items on listbox reflects any changes entered into textboxes after Update button clicked. So after lines like this:
textbox1.DataBindings["Text"].WriteValue();
textbox2.DataBindings["Text"].WriteValue();
just insert this line:
bindingsourcevariable.EndEdit();
Hope this helps others who also encounter similar problem but haven't found the right solution
Here is solution code that does everything in XAML as opposed to back end C#. This is how I do my projects utilizing MVVM (minimizing the back end code, and if possible having no back end code)
<ListBox x:Name="lstServers" HorizontalAlignment="Left" Height="285" Margin="20,37,0,0" VerticalAlignment="Top" Width="215"
ItemsSource="{Binding Settings.Servers}"
SelectedItem="{Binding Settings.ManageSelectedServer, Mode=TwoWay}"
DisplayMemberPath="UserFriendlyName"/>
This is a listbox on the Window. The keys to point out here, which can be very tricky, are the usual ItemsSource property being set to a Settings object on my view model, which has a Servers Observable collection.
Servers is a class that has a property called UserFriendlyName.
public sealed class AutoSyncServer : ObservableModel
{
public AutoSyncServer()
{
Port = "80";
UserFriendlyName = "AutoSync Server";
Server = "localhost";
}
private string _userFriendlyName;
public string UserFriendlyName
{
get { return _userFriendlyName;}
set
{
_userFriendlyName = value;
OnPropertyChanged("UserFriendlyName");
}
}
This is a partial code snippet for you of the class itself.
The SelectedItem of the ListBox is bound to an instance of the Selected object that I store in the model view called ManageSelectedServer.
The tricky part here is the DisplayMemberPath is set to "UserFriendlyName" as opposed to "{Binding UserFriendlyName}". This is key
If you use {Binding UserFriendlyName} it will display the UserFriendlyNames in the collection but will not reflect any changes to that property.
The XAML for the TextBox where the user can update the user friendly name (which should change the text in the listbox also) is:
<TextBox x:Name="txtDisplayName" HorizontalAlignment="Left" Height="23" Margin="395,40,0,0" TextWrapping="Wrap"
Text="{Binding ElementName=lstServers,Path=SelectedItem.UserFriendlyName, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top" Width="240"/>
This sets the Text property of the TextBox and binds it to the ListBox element lstServers SelectedItem property UserFriendlyName. I've also included an UpdateSourceTrigger=PropertyChanged so that any changes made to the text source notify that they have been changed.
XAML is tricky!