Wpf Mvvm ComboBox - wpf

I am new in the Wpf world, so I created a couple of views and all of them have at least one ComboBox, as I am using the MvvM pattern, I get my self re-typing all the time the same line of codes to fill the Combo and to get the SelectedItem (creating properties, privates for fill and other to get).
Is there some kind of framework that can improve this part ? or hack/trick ??? as I see too much repetitive code... maybe I am doing something wrong, take a look:
XAML:
<ComboBox name= "cbDepartments" DisplayMemberPath="DepartmentName"
SelectedValuePath ="PrimaryKey"
ItemsSource="{Binding Path=Departments}"
SelectedItem="{Binding Path=DefaultBranch,Mode=TwoWay}"
>
ViewModel:
private Department defaultBranch;
public Department DefaultBranch
{
get
{
return this.defaultBranch;
}
set
{
if (this.defaultBranch != value)
{
this.defaultBranch = value;
this.OnPropertyChanged("DefaultBranch");
this.saveChangeCommand.RaiseCanExecuteChanged();
this.UserMessage = string.Empty;
}
}
}
private ObservableCollection<Department> departments;
public ObservableCollection<Department> Departments
{
get { return this.departments; }
set
{
if (this. departments!= value)
{
this. departments = value;
this.OnPropertyChanged("Departments");
}
}
}

Most of what you have looks pretty standard. There are a few things you can cut down:
It looks like you aren't using SelectedValue so you can remove SelectedValuePath
SelectedItem is TwoWay by default so you can remove the Mode=TwoWay from that binding
For the departments property you should be able to remove the setter entirely and instead add and remove items in the existing collection. This can also help to avoid issues with ItemsSource bindings not getting correct notifications - INotifyCollectionChanged works more consistently that INotifyPropertyChanged on the collection property. Departments could collapse down to:
public ObservableCollection<Department> Departments { get; private set; }

As for making a custom control for the combobox with departments - that is really easy in WPF:
<ComboBox DisplayMemberPath="DepartmentName" x:Class="...DepartmentComboBox"
SelectedValuePath ="PrimaryKey"
ItemsSource="{Binding Path=Departments}"
SelectedItem="{Binding Path=DefaultBranch,Mode=TwoWay}"/>
And Code-behind:
public partial class DepartmentComboBox
{
public DepartmentComboBox()
{
InitializeComponent();
}
}

Related

WPF: Disable the selected item in ComboBox

I have been going through posts for 3 hours now with no resolution. I am new to WPF and created the ComboBox below:
Unfortunately I cannot disable the highlighting of the selected item. Does anyone have a viable solution?
Code:
<StackPanel Grid.Column="1"
Margin="800,0,0,0"
Width="135"
HorizontalAlignment="Right"
VerticalAlignment="Center">
<ComboBox Name="LangComboBox"
IsEditable="True"
IsReadOnly="True"
Text="Select Language">
<ComboBoxItem>English</ComboBoxItem>
<ComboBoxItem>Spanish</ComboBoxItem>
<ComboBoxItem>Both</ComboBoxItem>
</ComboBox>
</StackPanel>
I would like to clarify first of all that mine wants to be constructive answer and want to try to spread the culture of good programming.
We all have always to learn about programming, me too!
If you do not know a topic, it is good practice to study perhaps starting from a good book or from the official documentation of the platform.
That said let's move on to some possible approaches to your problem.
First of all, the fact that the selection in the combobox is that way is due to the basic template of the combobox that I invite you to view: https://msdn.microsoft.com/library/ms752094(v=vs.85).aspx )
What you are looking for is a different behavior of the combobox:
Allow display of a default value
Once an element is selected, the text inside it is not underlined
A first approach could be based on the ComboBox template: the combobox is constructed in such a way that, if it is editable, its template
contains a textbox called PART_EditableTextBox
by acting on the textbox, for example by making it disabled, you can get the result you want.
And this can be implemented in different ways:
Inserting a code-behind event handler that disables the textbox when the combobox is loaded
With an Attached behavior that allows you to add custom behaviors to the controls (https://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF)
Write a custom control that maybe insert a watermark type part to your combobox
Now consider the first approach that is the fastest to implement so the code could be the following:
<ComboBox Name="LangComboBox" IsEditable="True" IsReadOnly="True"
Loaded="LangComboBox_Loaded"
Text="Select language">
<ComboBoxItem Content="English"/>
<ComboBoxItem Content="Spanish"/>
<ComboBoxItem Content="Both"/>
</ComboBox>
In the code-behind:
private void LangComboBox_Loaded(object sender, RoutedEventArgs e)
{
ComboBox ctrl = (ComboBox)sender;
TextBox Editable_tb = (TextBox)ctrl.Template.FindName("PART_EditableTextBox", ctrl);
if (Editable_tb != null)
{
// Disable the textbox
Editable_tb.IsEnabled = false;
}
}
This approach, however, has drawbacks, among which the fact that if the user wants to deselect / reset the value of the combo can not do it.
So you could follow another path using the MVVM pattern.
Coming from the world of web programming you should know the MVC pattern, in WPF the most common pattern is MVVM or Model - View - ViewModel
between the two patterns there are different things in common and I invite you to take a look at them: Mvvm Pattern.
You could create a class with the model that will be hosted in the combo for example:
public class Language
{
public int Id { get; set; }
public string Description { get; set; }
public Language(int id, string desc)
{
this.Id = id;
this.Description = desc;
}
}
public class YourDataContext : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<Language> _Languages;
public List<Language> Languages
{
get
{
return _Languages;
}
set
{
_Languages = value;
OnPropertyChanged("Languages");
}
}
private Language _selectedLanguage;
public Language SelectedLanguage
{
get
{
return _selectedLanguage;
}
set
{
_selectedLanguage = value;
OnPropertyChanged("SelectedLanguage");
}
}
public YourDataContext()
{
// Initialization of languages
Languages = new List<Language>();
Languages.Add(new Language(0, "None - Select a Language"));
Languages.Add(new Language(1, "English"));
Languages.Add(new Language(2, "Spanish"));
Languages.Add(new Language(3, "Both"));
SelectedLanguage = Languages.First();
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
// some other properties and commands
}
// Your Window class
public MainWindow()
{
InitializeComponent();
var dc = new YourDataContext();
DataContext = dc;
}
<ComboBox ItemsSource="{Binding Languages}"
DisplayMemberPath="Description"
SelectedItem="{Binding SelectedLanguage}"/>
Note that now the combobox is no longer editable and it is possible to reset the selection.
You can manage the selection using the model:
if(dc.SelectedLanguage.Id == 0)
{
//No language selected
}
There are a lot of different ways to achieve what you want, i hope this gave you some good point to start from.
Good programming to everyone.

equivalent code in wpf

In Winforms I used the below code to select the specific item in DataGridView.
If DGView.Rows(row).Cells(0).Value.StartsWith(txtBoxInDGView.Text, StringComparison.InvariantCultureIgnoreCase) Then
DGView.Rows(row).Selected = True
DGView.CurrentCell = DGView.SelectedCells(0)
End If
Can anyone give the equivalent code for WPF DataGrid?
WPF is more data-driven than WinForms. It means it's better to work with objects (that represent your data) than to deal with UI elements.
You should have a collection that is the items source of the data grid. In the same data context, you should have a property that will hold the selected item (same type as the items in the collection). All properties should notify change.
Considering you have MyItem class for each row in data grid, the code would be something like this:
In the class that is the data context of your data grid:
public ObservableCollection<MyItem> MyCollection {get; set;}
public MyItem MySelectedItem {get; set;} //Add change notification
private string _myComparisonString;
public string MyComparisonString
{
get{return _myComparisonString;}
set
{
if _myComparisonString.Equals(value) return;
//Do change notification here
UpdateSelection();
}
}
.......
private void UpdateSelection()
{
MyItem theSelectedOne = null;
//Do some logic to find the item that you need to select
//this foreach is not efficient, just for demonstration
foreach (item in MyCollection)
{
if (theSelectedOne == null && item.SomeStringProperty.StartsWith(MyComparisonString))
{
theSelectedOne = item;
}
}
MySelectedItem = theSelectedOne;
}
In your XAML, you'd have a TextBox and a DataGrid, similar to this:
<TextBox Text="{Binding MyComparisonString, UpdateSourceTrigger=PropertyChanged}"/>
....
<DataGrid ItemsSource="{Binding MyCollection}"
SelectedItem="{Binding MySelectedItem}"/>
This way, your logic is independent from your UI. As long as you have change notification - the UI will update the properties and the properties will affect the UI.
[Treat code above as a pseudo-code, I'm not on my dev machine currently]

Binding a save command WPF

I have a window with 3 textboxes in a grid -this is my view- and I have Save button to add a new user to my user list with the datas from the textboxes.
I want to use a relay command to do this on my viewmodel class but I am quite confused with how to make the bindings. I hope it's clear enough. Any ideas, or examples will be helpful.
thanks in advance.
You should have a ViewModel something like the following :
class UserViewModel
{
public String Name { get; set; }
public String Password { get; set; }
public String Email { get; set; }
public RelayCommand AddUserCommand { get; set; }
public UserViewModel()
{
AddUserCommand = new RelayCommand(AddUser);
}
void AddUser(object parameter)
{
// Code to add user here.
}
}
And you can use it like following :
<StackPanel>
<TextBox Text="{Binding Name}"></TextBox>
<TextBox Text="{Binding Password}"></TextBox>
<TextBox Text="{Binding Email}"></TextBox>
<Button Command="{Binding AddUserCommand}">Add</Button>
</StackPanel>
To make this work, put following code in your UserControl/Control/Window's constructor :
DataContext = new UserViewModel();
I presume that you read Josh Smith article: WPF Apps With The Model-View-ViewModel Design Pattern. If you didn't, then read it first, and then download code, because example is very similar to your problem.
Did you created an instance of the ViewModel and putted this instance in the DataContext of your view or stackpanel?
example:
UserViewModel viewModel = new UserViewModel();
UserWindow view = new UserWindow();
view.DataContext = viewModel;
view.Show();
There are several options on coupling the View and the Viewmodel:
Create the View and ViewModel and set the ViewModel to the DataContext property (code above)
Create the ViewModel in the constructor of the View and fill the DataContext property with it
Create a Resource in your view of the type of your ViewModel and fill the DataContext property in XAML
I prefer the first option because you can combine the Views and Viewmodels as you like at runtime.
Hopefully this is a helpfull answer.

XAML Binding to a CollectionViewSource property on a ViewModel

I have a simple ViewModel like:
public class MainViewModel {
ObservableCollection<Project> _projects;
public MainViewModel() {
// Fill _projects from DB here...
ProjectList.Source = _projects;
ProjectList.Filter = ...;
}
public CollectionViewSource ProjectList { get; set; }
}
I set the window's DataContext to a new instance of that ViewModel in the constructor:
public MainWindow() {
this.DataContext = new MainViewModel();
}
Then in the Xaml I am attempting to bind the ItemsSource of a ListBox to that ProjectList property.
Binding just ItemsSource like so doesn't work:
<ListBox ItemsSource="{Binding ProjectList}" ItemTemplate="..." />
But if I first rebase the DataContext this works:
<ListBox DataContext="{Binding ProjectList}" ItemsSource="{Binding}" ItemTemplate="..." />
Shouldn't the first method work properly? What might I be doing wrong?
If you are using CollectionViewSource you need to bind ItemsSource to ProjectList.View instead of ProjectList. That should solve your problem.
From what you provided the first method should perfectly work. Devil lurks somewhere in details.
PS: Maybe you didn't specify implementation of INotifyPropertyChanged interface in sake of post size, but be careful in production. It's very easy to get a memory leak if you don't implement it.

WPF ComboBox SelectedItem Set to Null on TabControl Switch

I've got a simple problem in my WPF application which has me banging my head on the table. I have a TabControl, where every TabItem is a View generated for a ViewModel using a DataTemplate similar to this:
<DataTemplate DataType="{x:Type vm:FooViewModel}">
<vw:FooView/>
</DataTemplate>
FooView contains a ComboBox:
<ComboBox ItemsSource="{Binding Path=BarList}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedBar}"/>
and FooViewModel contains a simple Property: public Bar SelectedBar { get; set; }. My problem is that when I set the value for my ComboBox, change to another tab, then change back, the ComboBox is empty again. If I set a breakpoint on the setter for my property, I see that the property is assigned to null when I switch to another tab.
From what I understand, when a tab is switched, it is removed from the VisualTree - but why is it setting my ViewModel's property to null? This is making it very difficult for me to hold persistent state, and checking value != null does not seem like the right solution. Can anyone shed some like on this situation?
Edit: The call stack at the setter breakpoint only shows [External Code] - no hints there.
We just ran into the same problem. We found a blog entry describing the problem. It looks like it is a BUG in WPF and there is a workaround:
Specify the SelectedItem binding before the ItemsSource binding and the problem should be gone.
blog article
TLDR;
Change:
<ComboBox ItemsSource="{Binding Countries, Mode=OneWay}"
SelectedItem="{Binding SelectedCountry}"
DisplayMemberPath="Name" >
</ComboBox>
To:
<ComboBox SelectedItem="{Binding SelectedCountry}"
ItemsSource="{Binding Countries, Mode=OneWay}"
DisplayMemberPath="Name" >
</ComboBox>
My app is using avalondock & prims and had that exact problem. I has same thought with BSG, when we switched tab or document content in MVVM app, the controls as listview+box, combobox is removed from VisualTree. I bugged and saw most data of them was reset to null such as itemssource, selecteditem, .. but selectedboxitem was still hold current value.
A approach is in model, check its value is null then return like this:
private Employee _selectedEmployee;
public Employee SelectedEmployee
{
get { return _selectedEmployee; }
set
{
if (_selectedEmployee == value ||
IsAdding ||
(value == null && Employees.Count > 0))
{
return;
}
_selectedEmployee = value;
OnPropertyChanged(() => SelectedEmployee);
}
But this approach can only solve quite good in first binding level. i mean,
how we go if want to bind SelectedEmployee.Office to combobox, do same is not good
if check in propertyChanged event of SelectedEmployee model.
Basically, we dont want its value is reset null, keep its pre-value. I found a new solution
consistently. By using attached property, i created KeepSelection a-Pro, bool type, for Selector controls, thus supply all its inherited suck as listview, combobox...
public class SelectorBehavior
{
public static bool GetKeepSelection(DependencyObject obj)
{
return (bool)obj.GetValue(KeepSelectionProperty);
}
public static void SetKeepSelection(DependencyObject obj, bool value)
{
obj.SetValue(KeepSelectionProperty, value);
}
// Using a DependencyProperty as the backing store for KeepSelection. This enables animation, styling, binding, etc...
public static readonly DependencyProperty KeepSelectionProperty =
DependencyProperty.RegisterAttached("KeepSelection", typeof(bool), typeof(SelectorBehavior),
new UIPropertyMetadata(false, new PropertyChangedCallback(onKeepSelectionChanged)));
static void onKeepSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = d as Selector;
var value = (bool)e.NewValue;
if (value)
{
selector.SelectionChanged += selector_SelectionChanged;
}
else
{
selector.SelectionChanged -= selector_SelectionChanged;
}
}
static void selector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selector = sender as Selector;
if (e.RemovedItems.Count > 0)
{
var deselectedItem = e.RemovedItems[0];
if (selector.SelectedItem == null)
{
selector.SelectedItem = deselectedItem;
e.Handled = true;
}
}
}
}
Final, i use this approach simply in xaml:
<ComboBox lsControl:SelectorBehavior.KeepSelection="true"
ItemsSource="{Binding Offices}"
SelectedItem="{Binding SelectedEmployee.Office}"
SelectedValuePath="Id"
DisplayMemberPath="Name"></ComboBox>
But, selecteditem will never null if selector's itemssource has items. It may affect
some special context.
Hope that helps.
Happy conding! :D
longsam
I had this same problem when scrolling through a virtualizing DataGrid that contains ComboBoxes. Using IsSynchronizedWithCurrentItem did not work, nor did changing the order of the SelectedItem and ItemsSource bindings. But here is an ugly hack that seems to work:
First, give your ComboBox an x:Name. This should be in the XAML for a control with a single ComboBox. For example:
<ComboBox x:Name="mComboBox" SelectedItem="{Binding SelectedTarget.WritableData, Mode=TwoWay}">
Then add these two event handlers in your codebehind:
using System.Windows.Controls;
using System.Windows;
namespace SATS.FileParsing.UserLogic
{
public partial class VariableTargetSelector : UserControl
{
public VariableTargetSelector()
{
InitializeComponent();
mComboBox.DataContextChanged += mComboBox_DataContextChanged;
mComboBox.SelectionChanged += mComboBox_SelectionChanged;
}
/// <summary>
/// Without this, if you grab the scrollbar and frantically scroll around, some ComboBoxes get their SelectedItem set to null.
/// Don't ask me why.
/// </summary>
void mComboBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
mComboBox.GetBindingExpression(ComboBox.SelectedItemProperty).UpdateTarget();
}
/// <summary>
/// Without this, picking a new item in the dropdown does not update IVariablePair.SelectedTarget.WritableData.
/// Don't ask me why.
/// </summary>
void mComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
mComboBox.GetBindingExpression(ComboBox.SelectedItemProperty).UpdateSource();
}
}
}
Generally, I use SelectedValue instead of SelectedItem. If I need the object associated with the SelectedValue then I add a lookup field containing this to the target object (as I use T4 templates to gen my viewmodels this tends to be in a partial class). If you use a nullable property to store the SelectedValue then you will experience the problem described above, however if binding the SelectedValue to a non-nullable value (such as an int) then the WPF binding engine will discard the null value as being inappropriate for the target.
Edit:
Below stuff works (I hope...); I developed it because I followed the SelectedItems route described on the MVVM Lite page. However - why do I want to rely on SelectedItems? Adding an IsSelected property to my Items (as shown here) automatically preserves selected items (short of the mentioned cavet in above link). In the end, much easier!
Inital Post:
ok - that was a piece of work; I've a multi-column ListView with SelectionMode="Extension", which makes the whole thing fairly complex. My starting point is invoking tabItems from workspaces similar as describe here.
I made sure that in my ViewModel, I know when a tab item (workspace) is active. (This is a bit similar to here) - of course, somebody needs initalize SelectedWorkspace first.
private Int32 _selectedWorkspace;
public Int32 SelectedWorkspace {
get { return _selectedWorkspace; }
set {
_selectedWorkspace = value;
base.OnPropertyChanged("SelectedWorkspace");
}
}
protected Int32 _thisWorkspaceIdx = -1;
protected Int32 _oldSelectedWorkspace = -1;
public void OnSelectedWorkspaceChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == "SelectedWorkspace") {
if (_oldSelectedWorkspace >= 0) {
Workspaces[_oldSelectedWorkpace].OnIsActivatedChanged(false);
}
Workspaces[SelectedWorkspace].OnIsActivatedChanged(true);
_oldSelectedWorkspace = SelectedWorkspace;
}
}
protected bool _isActive = false;
protected virtual void OnIsActivatedChanged(bool isActive) {
_isActive = isActive;
}
This allowed me to update the ViewModel selected items only if the tab item (workspace) was actually active. Hence, my ViewModel selected items list is preserved even as the tab item clears the ListView.SelectedItems. In the ViewModel:
if (_isActive) {
// ... update ViewModel selected items, referred below as vm.selectedItems
}
Last, when the tabItem got re-enabled, I hooked up to the 'Loaded' event and restored the SelectedItems. This is done in the code-behind of the View. (Note that whilst my ListView has multiple columns, one serves as a key, the others are for information only. the ViewModel selectedItems list only keeps the key. Else, the comparison below would be more complex):
private void myList_Loaded(object sender, RoutedEventArgs e) {
myViewModel vm = DataContext as myViewModel;
if (vm.selectedItems.Count > 0) {
foreach (string myKey in vm.selectedItems) {
foreach (var item in myList.Items) {
MyViewModel.MyItem i = item as MyViewModel.MyItem;
if (i.Key == myKey) {
myList.SelectedItems.Add(item);
}
}
}
}
}
if you suing async selection in WPF then remove it IsSynchronizedWithCurrentItem="True" from for the ComboBox, please refer to the document about IsSynchronizedWithCurrentItem:
<ComboBox
Name="tmpName"
Grid.Row="10"
Width="250"
Text="Best Match Position List"
HorizontalAlignment="Left"
Margin="14,0,0,0"
SelectedItem="{Binding Path=selectedSurceList,Mode=TwoWay}"
ItemsSource="{Binding Path=abcList}"
DisplayMemberPath="Name"
SelectedValuePath="Code"
IsEnabled="{Binding ElementName=UserBestMatchYesRadioBtn,Path=IsChecked}">
</ComboBox>
also takecare the binding
first use SelectedItem
then ItemsSource
ref:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/fb8a8ad2-83c1-43df-b3c9-61353979d3d7/comboboxselectedvalue-is-lost-when-itemssource-is-updated?forum=wpf
http://social.msdn.microsoft.com/Forums/en-US/c9e62ad7-926e-4612-8b0c-cc75fbd160fd/bug-in-wpf-combobox-data-binding
I solve my problem using the above
I once had a similar problem. It seems that the combobox looses the selected item in VisibilityChanged event. Workarround is to clear the binding before this occurs, and reset it when coming back. You can also try to set the Binding to Mode=TwoWay
Hope that this helps
Jan
I had the same problem and solved it with the following method attached to the Combobox DataContextChanged-Event:
private void myCombobox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement && e.NewValue == null)
((FrameworkElement)sender).DataContext = e.OldValue;
}
So everytime you want to remove the datacontext from the combobox, the old datacontext will be set again.
Everytime you change the active Tab of your TabControl, the Combobox will removed from your VisualTree and added if you go back to the one with your combobox. If the combo box is removed from the VisualTree, also the DataContext is set to null.
Or you use a class, which have implemented such feature:
public class MyCombobox : ComboBox
{
public MyCombobox()
{
this.DataContextChanged += MyCombobox_DataContextChanged;
}
void MyCombobox_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if (sender is FrameworkElement && e.NewValue == null)
((FrameworkElement)sender).DataContext = e.OldValue;
}
public void SetDataContextExplicit(object dataContext)
{
lock(this.DataContext)
{
this.DataContextChanged -= MyCombobox_DataContextChanged;
this.DataContext = dataContext;
this.DataContextChanged += MyCombobox_DataContextChanged;
}
}
}
I think the problem may be that you arent telling the Combo box when to bind back to the source. Try this:
<ComboBox ItemsSource="{Binding Path=BarList}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedBar, UpdateSourceTrigger=PropertyChanged}"/
You can use the MVVM framework Catel and the catel:TabControl element there this problem is already solved.
Just don't allow your ViewModel's property to be changed if value becomes null.
public Bar SelectedBar
{
get { return barSelected; }
set { if (value != null) SetProperty(ref barSelected, value); }
}
That's it.

Resources