I have a really weird issue with Combobox in WPF. I have used it before in a simpler implementation and thought I knew how it works, but apparently not. The scenario is as follows... I have three classes which are in a parent/child relationship, I have a list of a type AnswerViewModel which I populate in a Combobox. I want the IsSelected property on the class to be the value that is get/set in the Combobox. Below is some of the code, hopefully enough to see what I am referring to.
These are the models.
public class Quiz
{
public string QuizName {get;set;}
public List<Question> Questions {get;set;}
}
public class Question
{
public int QuestionId {get;set;}
public string QuestionText {get;set;}
public List<Answer> {get;set;}
}
public class Answer
{
public int AnswerId {get;set;}
public string AnswerText {get;set;}
public bool IsSelected {get;set;}
}
These are the ViewModels
QuizViewModel.cs
// This will create QuestionViewModel for each question.
ObservableCollection<QuestionViewModel> Questions {get;set;}
QuestionViewModel.cs
// This will create AnswerViewModel for each answer
ObservableCollection<AnswerViewModel> Answers {get;set;}
AnswerViewModel.cs
Answer answer;
// ctor
public AnswerViewModel(Answer answer...)
{
this.answer = answer;
...
}
public string AnswerText
{
get { return answer.AnswerText; }
set
{
answer.AnswerText = value;
NotifyPropertyChanged("AnswerText");
}
}
public bool IsSelected
{
get { return _answer.IsSelected; }
set
{
answer.IsSelected = value;
NotifyPropertyChanged("IsSelected"); // <--- Breakpoint
}
}
These are the XAMLs
Quiz.xaml
<ItemsControl ItemsSource="{Binding Questions}" />
Question.xaml
<ComboBox x:Name="answerCombo"
ItemsSource="{Binding Answers}"
DisplayMemberPath="AnswerText"
SelectedValue="{Binding Path=IsSelected}"
SelectedValuePath="IsSelected"/>
The Combobox is populated correctly (It is bound to the Collection of AnswerViewModel), but when I make a selection what I expect is to see at the breakpoint is two events. One for the false on the item no longer selected, and true for the new item selected. But instead it never gets hit.
So I scour the web looking for help and found some interesting information and tried a number of things but, so far nothing seems to be complete. For example:
<ComboBox x:Name="answerCombo"
ItemsSource="{Binding Answers}"
DisplayMemberPath="AnswerText"
SelectedValue="{Binding Path=IsSelected}"
SelectedValuePath="IsSelected">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ComboBox.ItemContainerStyle>
</Combobox>
But this only selects the correct item in the Combobox when drop-down. Which I understand is correct for this markup.
So, in short, I would like to have the IsSelected Property on the AnswerViewModel updated when I select an item in the Combobox. Any help would be greatly appreciated.
If more information is required just let me know.
Thank you,
Obscured
** Update **
Ok, I think I actually resolved it, but still If anyone has suggestions, or thoughts on this, I would still like to hear them.
Here is what I did, I only changed the Combobox xaml as follows:
<ComboBox x:Name="answerCombo"
ItemsSource="{Binding Answers}"
DisplayMemberPath="AnswerText"
SelectedValue="{Binding Path=ItemsSource/, RelativeSource={RelativeSource Self}}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ComboBox.ItemContainerStyle>
</Combobox>
Basically this sets the selectedvalue to the current item, and since the itemcontainerstyle handles the case when drop-down it binds the IsSelected property of the combobox to the IsSelected property of my ViewModel. And this appears to do the trick.
Just remove the SelectedValue and SelectedValuePath properties, it's not how they work. SelectedValuePath indicates which property of the selected item will be the SelectedValue of the ComboBox; if you say that the SelectedValuePath is "IsSelected", the SelectedValue will be SelectedItem.IsSelected, which is clearly not what you want... And your binding for SelectedValue refers to a property that doesn't exist (QuestionViewModel.IsSelected)
This should work for you:
<ComboBox x:Name="answerCombo"
ItemsSource="{Binding Answers}"
DisplayMemberPath="AnswerText">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ComboBox.ItemContainerStyle>
</Combobox>
have you try:
<ComboBox x:Name="answerCombo"
ItemsSource="{Binding Answers}"
DisplayMemberPath="AnswerText"
SelectedValue="{Binding IsSelected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="IsSelected"/>
Related
In the following class, the itemssource of a listbox should bind to the Interfaces property.
public class BaseClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private const string TYPE_TITLE = "Type";
private string _Type;
public string Type
{
get { return _Type; }
set { _Type = value; this.NotifyPropertyChanged(PropertyChanged, TYPE_TITLE); }
}
public ObservableCollection<string> Interfaces { get; set; }
public BaseClass()
{
Interfaces = new ObservableCollection<string>();
}
public void Reset()
{
_Type = null;
Interfaces.Clear();
}
}
In that list box the selected item should be able to edit as the inline edit scenario,
<DataTemplate x:Key="BaseClass_Interfaces_InlineEdit_Template">
<TextBox Text="{Binding Mode=TwoWay, Path=., NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}" TextChanged="TextBox_TextChanged"/>
</DataTemplate>
<DataTemplate x:Key="BaseClass_Interfaces_InlineView_Template">
<TextBlock Text="{Binding Path=., UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}" x:Key="BaseClass_Iterfaces_ItemStyle_Template">
<Setter Property="ContentTemplate" Value="{StaticResource BaseClass_Interfaces_InlineView_Template}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource BaseClass_Interfaces_InlineEdit_Template}" />
</Trigger>
</Style.Triggers>
</Style>
The ListBox has a container as a parent hierarchy which its DataContext property bind to an instance of BaseClass hence the ListBox could bind to the Interfaces property.
<ListBox Grid.Row="2" Grid.ColumnSpan="2" Margin="3" ItemsSource="{Binding Interfaces, Mode=TwoWay}" SelectionMode="Single"
ItemContainerStyle="{StaticResource ResourceKey=BaseClass_Iterfaces_ItemStyle_Template}" />
The list box before select any item
Editing the selected item
Another item select after edit and the changes doesn't affected
There are two problems :
The TextBox should have "Path=." otherwise the "Two-way binding requires Path or XPath." exception message received.
With consider the above problem, the ObservableCollection items never updated after text changed!!!!!!
I found the answer!
My wondering was about the text box which the text property changed but the changes does not propagated to the source, based on the link the binding mechanism works on the properties of sources in the other words the change of the properties monitors not the object itself.
The solution is a wrapper class around the string, i wrote this wrapper before for another primitive type (bool).
public class Wrapper<T>
{
public T Item { get; set; }
public Wrapper(T value = default(T))
{
Item = value;
}
public static implicit operator Wrapper<T>(T item)
{
return new Wrapper<T>(item);
}
public static implicit operator T(Wrapper<T> item)
{
if (null != item)
return item.Item;
return default(T);
}
}
So the editing data template change as follow
<DataTemplate x:Key="BaseClass_Interfaces_InlineEdit_Template">
<TextBox Text="{Binding Mode=TwoWay, Path=Item, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
And every thing work as charm!!!
I've been searching for a while and haven't been able to find a solution.
My goal is to bind a property in my ViewModel to the selected item of a listbox in my custom control.
First off - This calendar control has been sourced from Jarloo Custom Calendar. I have merely been modifying it for custom personal use.
The calendar control (abbreviated) is as below:
<ResourceDictionary>
<Style TargetType="{x:Type Neptune:Calendar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Neptune:Calendar}">
<!--Some header items and other misc controls....-->
<DockPanel>
<!--Calendar-->
<ListBox ItemsSource="{Binding Days}" Background="{x:Null}"
SelectedItem="{Binding SelectedDay, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And the local (abbreviated):
public class Calendar : Control
{
public ObservableCollection<Day> Days { get; set; }
//SelectedDay Property
public static readonly DependencyProperty SelectedDayProperty =
DependencyProperty.Register("SelectedDay", typeof(Day), typeof(Calendar),
new PropertyMetadata(null, OnSelectedDayChanged));
public Day SelectedDay
{
get { return (Day)GetValue(SelectedDayProperty); }
set { SetValue(SelectedDayProperty, value); }
}
private static void OnSelectedDayChanged(DependencyObject pager, DependencyPropertyChangedEventArgs e)
{
Calendar d = pager as Calendar;
//MessageBox.Show(d.SelectedDaDateString());///THIS SHOWS CORRECT SELECTED DATE!!!!
d.SetValue(ThisDayProperty, d.SelectedDay);
}
In the window that utilizes this control, I have tried binding to this SelectedDay Property and even made another DP to pass the value to just for testing. Neither values are binding correctly.
<Neptune:Calendar Grid.Row="1" x:Name="Calendar" Margin="0,10,0,0"
ThisDay="{Binding DaySelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}">
I don't understand... Why is this not working?
All I want to do is expose a property that reflects the selected item in the calendar list of Days.
I posted the question on the Microsoft forums and received the correct answer from Magnus.
https://social.msdn.microsoft.com/Forums/vstudio/en-US/d0d8362e-8341-410c-8364-557c5b4345d0/binding-to-nested-listbox-selecteditem-in-custom-control?forum=wpf
I have one problem using TreeView in WPF using the MVVM design pattern.I found a solution that helped me a lot for understanding the principals, but when I applied it in my project, I can't get the desired results. The article is this one.
My base class is:
public class ClientTreeViewProducts
{
public List<ClientTreeViewProducts> Items { get; set; }
public string Name { get; set; }
}
In the ViewModel I have a public property that I bind to the ItemsSource property of the TreeView:
private List<ClientTreeViewProducts> _treeViewSource;
public List<ClientTreeViewProducts> TreeViewSource
{
get { return _treeViewSource; }
set
{
if(_treeViewSource!=value)
{
_treeViewSource = value;
RaisePropertyChanged("TreeViewSource");
}
}
}
I have a function that fills the List, and it seems to be OK.
And finally here is my xaml that manages the bindings and the HierarchicalDataTemplate :
<TreeView ItemsSource="{Binding TreeViewSource}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type helpers:ClientTreeViewProducts}"
ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The problem is when I fill some data in the TreeViewSource property, the tree view remains empty (visually). Can someone find what the problem is, because I'm head-banging about this problem for 3 days.
Thank you in advance!
Please help me figure out how to bind custom class collection to datagrid combobox.
My custom class is
class Test: INotifyPropertyChanged
{
public String Name { get; set; }
public UserAvailableValue SelectedAvailableValue { get; set; }
public ObservableCollection<UserAvailableValue> AvailableValues { get; set; }
public ObservableCollection<String> DefaultValues { get; set; }
public String SelectedValue { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class UserAvailableValue
{
public Object Value { get; set; }
public Object Label { get; set; }
}
From code behind, i am setting DataGrid Datacontext i.g.
ObservableCollection<Test> UIParams = new ObservableCollection<Test>();
// code to fill UIParams collection
dgReportparameters.DataContext = UIParams;
//XAML Code
<DataGrid Name="dgReportparameters" ItemsSource="{Binding}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridComboBoxColumn Header="Available Values" SelectedItemBinding=
"{Binding SelectedAvailableValue, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Label">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=AvailableValues,
RelativeSource={RelativeSource AncestorType=Window}}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
</DataGridComboBoxColumn>
<DataGridTextColumn Header="Default Values" Binding="{Binding SelectedValue}"/>
<DataGridCheckBoxColumn Header="Nullable" Binding="{Binding IsNullable}"/>
</DataGrid.Columns>
</DataGrid>
Except DataGridComboBoxColumn other columns are showing correct values.DataGridComboBoxColumn is showing blank column. UIParams collection has multiple parameters while each parameter has name and Available values and one default value. I want to show paramters in datagrid and let user select one/multiple values from Available column combobox.
Each parameter has its own set of available values. Most of the example i found have Common collection in dropdown but in my case each row of datagrid has different available values.
Please help me to have combobox in datagrid.
Thanks in advance.
Your Combobox Style is wrong. The ItemsSource is set to Window whereas from your Model, you don't need to set the AncestorType.
Just {Binding} is enough. Actually, you don't need the ItemsSource Setter as you are just defining the style of the Combobox. You need it only if you are modifying the ItemsSource to something else. In your case, its not.
Edit:
Take look on this example: Binding ItemsSource of a ComboBoxColumn in WPF DataGrid
I don't understand your requirement of having AvailableValues collection in each Data Item which you have have it in Top Level like below.
public class MyClass
{
public List<Test> Items{get;set;}
public List<AvailableValue> AvailableValues { get;set;}
}
I also notices that you have implemented INotifyPropertyChanged interface and not raising the change event on the each property set.
I would suggest you to learn some basic of WPF and INotifyPropertyChanged interface before you start working on them.
This helped me.
<DataGridComboBoxColumn Header="Available Values" SelectedItemBinding=
"{Binding SelectedAvailableValue, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Label">
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=AvailableValues}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
I have a ListView Contained in a UserControl I would like to disabled a button when no items are selected in the UserControl, would it be the right way to do it? So far, it doesn't disable, it just stays enable all the way.
I've included the xaml code.
searchAccountUserControl is the UserControl name property in the xaml.
And AccountListView is the ListView name property in the userControl xaml.
<Button Content="Debit" IsEnabled="true" HorizontalAlignment="Left" Margin="18,175,0,0" Name="DebitButton" Width="128" Grid.Column="1" Height="32" VerticalAlignment="Top" Click="DebitButton_Click">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=searchAccountUserControl.AccountListView, Path=SelectedValue}" Value="{x:Null}" >
<Setter Property="Button.IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Thanks.
Finally i've used :
in my ViewModel :
private bool _isSelected;
public bool IsSelected { get { return _isSelected; }
set { _isSelected = _account.View.CurrentItem != null;
PropertyChanged.SetPropertyAndRaiseEvent(this, ref _isSelected, value,
ReflectionUtility.GetPropertyName(() => IsSelected)); } }
And then Use isEnabled = "{Binding Path=IsSelected}" in the xaml.
There are a few things wrong here.
Precedence, if you set IsEnabled on the control itself the style will never be able to change it.
ElementName, it's an ElementName, not a path, just one string that gives the name of one element. Everything beyond that goes into the Path.
Style syntax, if you set a Style.TargetType you should not set the Setter.Property with a type prefix (although leaving it does not break the setter).
By the way, this alone is enough:
<Button IsEnabled="{Binding SelectedItems.Count, ElementName=lv}" ...
It's obvious that you aren't using Commanding (ICommand Interface). You should either use that (and preferably the Model-View-ViewModel architecture).
But, if you want to stick with code-behind and XAML:
<ListView SelectionChanged="AccountListView_SelectionChanged" ... />
private void AccountListView_SelectionChanged(Object sender, SelectionChangedEventArgs args)
{
DebitButton.IsEnabled = (sender != null);
//etc ...
}
More information on MVVM: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
You need to set the DataContext of the View (UserControl) to the instance of the ViewModel you want to use. Then, from there, you can bind to properties on the ViewModel, including ICommands. You can either use RelayCommand (see link above) or use Commanding provided by a framework (for example, Prism provides a DelegateCommand). These commands take an Action (Execute) and a Func (CanExecute). Simply provide the logic in your CanExecute. Of course, you'd also have to have your ListView SelectedItem (or SelectedValue) be databound to a property on your ViewModel so you can check to see if it's null within your CanExecute function.
Assuming you use RelayCommand you don't have to explicitly call the RaiseCanExecuteChanged of an ICommand.
public class MyViewModel : ViewModelBase //Implements INotifyPropertyChanged
{
public MyViewModel()
{
DoSomethingCommand = new RelayCommand(DoSomething, CanDoSomething);
}
public ObservableCollection<Object> MyItems { get; set; }
public Object SelectedItem { get; set; }
public RelayCommand DoSomethingCommand { get; set; }
public void DoSomething() { }
public Boolean CanDoSomething() { return (SelectedItem != null); }
}
<ListView ItemsSource="{Binding MyItems}" SelectedItem="{Binding SelectedItem}" ... />
<Button Command="{Binding DoSomethingCommand}" ... />