Issue with Combo box Item Template - wpf

Please find the below code.
public enum SortByType
{
[Display(Name = "Y Value")]
Y_Value,
[Display(Name = "X Value")]
X_Value,
[Display(Name = "Z Value")]
Z_Value,
}
public class ViewModel : INotifyPropertyChanged
{
ObservableCollection<SortByType> sortList;
SortByType selectedType;
public ViewModel()
{
SortList = new ObservableCollection<SortByType>(Enum.GetValues(typeof(SortByType)).OfType<SortByType>().ToList());
}
public ObservableCollection<SortByType> SortList
{
get
{
return sortList;
}
set
{
sortList = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SortList)));
}
}
public SortByType SelectedType
{
get
{
return selectedType;
}
set
{
selectedType = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedType)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new ViewModel();
(DataContext as ViewModel).SelectedType = SortByType.Y_Value;
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
(DataContext as ViewModel).SortList.Clear();
(DataContext as ViewModel).SortList = new ObservableCollection<SortByType>(Enum.GetValues(typeof(SortByType)).OfType<SortByType>().ToList());
}
}
<Grid>
<StackPanel VerticalAlignment="Center">
<ComboBox ItemsSource="{Binding SortList}"
SelectedItem="{Binding SelectedType}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Height="25" Width="150" Content="Details" Click="Button_Click"/>
</StackPanel>
</Grid>
Just, Clear the collection and re-initialize the combo box item source on button click event. On this time I have noticed that the Data error shown on the combo box. Can anyone explain what is the exact problem whether it may be a memory issue or some thing i have missed while implement the Item Template

The value of SelectedType must be present in the source collection. It's not when you assign the source property to a new list of new values, unless you set explicitly set it to any of these new values:
private void Button_Click(object sender, RoutedEventArgs e)
{
var viewModel = DataContext as Win32ViewModel;
viewModel.SortList = new ObservableCollection<SortByType>(Enum.GetValues(typeof(SortByType)).OfType<SortByType>().ToList());
viewModel.SelectedType = viewModel.SortList.FirstOrDefault(x => x == viewModel.SelectedType);
}

Related

DataGrid is not binding in WPF

I am trying to bind DataGrid using MVVM approach in WPF, model is getting values but nothing is showing in DataGrid
Following is my code
public class TalleyEditorGrid : INotifyPropertyChanged
{
#region Properties
private string _Quantity;
private string _Ft;
private string _Inch;
private string _Comment;
public string Quantity { get { return _Quantity; } set { _Quantity = value; NotifyPropertyChanged("Quantity"); } }
public string Ft { get { return _Ft; } set { _Ft = value; NotifyPropertyChanged("Ft"); } }
public string Inch { get { return _Inch; } set { _Inch = value; NotifyPropertyChanged("Inch"); } }
public string Comment { get { return _Comment; } set { _Comment = value; NotifyPropertyChanged("Comment"); } }
#endregion
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); }
}
In my xaml.cs
private ObservableCollection<TalleyEditorGrid> _TalleyEditorGrid = new ObservableCollection<TalleyEditorGrid>();
public ObservableCollection<TalleyEditorGrid> TalleyEditorCol
{
get { return _TalleyEditorGrid; }
}
On button click I am filling this collection
_TalleyEditorGrid.Add(new TalleyEditorGrid() { Quantity = Q, Ft = FT, Inch = In, Comment = Comment});
Xaml as follows
<DataGrid x:Name="TalleyEditor" ItemsSource="{Binding Path=TalleyEditorCol}" AutoGenerateColumns="True" Visibility="Collapsed"
CanUserAddRows="False"
HorizontalGridLinesBrush="{x:Null}"
VerticalGridLinesBrush="Silver"
Background="White"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
SelectionMode="Single"
SelectionUnit="FullRow"
CanUserReorderColumns="True"
CanUserSortColumns="True" DataGridCell.GotFocus="TalleyEditor_GotFocus"
RowHeaderWidth="0" HorizontalAlignment="Left" VerticalAlignment="Top" RowHeight="17" ColumnHeaderHeight="21" PreviewKeyDown="TalleyEditor_PreviewKeyDown" LostFocus="TalleyEditor_LostFocus" CellEditEnding="myDG_CellEditEnding">
Set the DataContext of the window to itself:
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
Or set the DataContext of the DataGrid:
public MainWindow()
{
InitializeComponent();
TalleyEditor.DataContext = this;
}
If you put the DataGrid inside another DataGrid, you need to use a {RelativeSource} to be able to bind to a property of the parent window:
<DataGrid ... ItemsSource="{Binding Path=TalleyEditorCol, RelativeSource={RelativeSource AncestorType=Window}}">
</DataGrid>

WpfCombo box itemsource binding call back

I have a combobox where it binded to view model list property. This list property then calls to the async function in data layer.
I wanted to set the selected index property of the combobox to "zero" I.e selected index=0;
Real scenario is data is perfectly loading but even after I set selected index property it is not applying since async call.
Please let me know any callback method after property bonded.
I have made a sample application
ViewModel
class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<string> _source;
private object _lock = new object();
public IEnumerable<string> Source
{
get { return _source; }
}
public String SelectedItem { get; set; }
public int SelectedIndex { get; set; }
public MainWindowViewModel()
{
_source = new ObservableCollection<string>();
BindingOperations.EnableCollectionSynchronization(_source, _lock);
Task.Factory.StartNew(() => PopulateSourceAsunc());
}
private void PopulateSourceAsunc()
{
for (int i = 0; i < 10; i++)
{
_source.Add("Test " + i.ToString());
Thread.Sleep(1000);
}
//SelectedItem = _source[6];
SelectedIndex = 5;
OnPropertyChanged("SelectedIndex");
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Xaml
<StackPanel>
<ComboBox ItemsSource="{Binding Source}"
SelectedItem="{Binding SelectedItem}"
SelectedIndex="{Binding SelectedIndex}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
</StackPanel>
Code behind
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
BindingOperations class is in the namespace of System.Windows.Data

How to get combobox to create a new object in response to text type in by the user?

I have items stored in the ItemSource property of a combobox, however, when the user types in a name that does not exist in the list I need it to create a new object and use that as the SelectedObject. I am pretty new to WPF and used to WindowsForms, so I might just be going about doing this the totally wrong way, any input is appreciated.
Xaml:
<Window x:Class="ComboExample.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">
<StackPanel>
<ComboBox x:Name="cboName" IsEditable="True" SelectionChanged="ComboBox_SelectionChanged"></ComboBox>
<DockPanel>
<Label>Selected Value</Label>
<TextBox Text="{Binding Name}"></TextBox>
</DockPanel>
<Button Click="Button_Click">Click Me</Button>
</StackPanel>
and code behind (which displays "value is null" if you type a new value in
class SomeClass
{
public SomeClass(string name) {this.Name = name;}
public string Name { get; set; }
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
cboName.ItemsSource = new SomeClass[] { new SomeClass("A"), new SomeClass("B") };
cboName.DisplayMemberPath = "Name";
cboName.SelectedItem = cboName.ItemsSource.OfType<SomeClass>().FirstOrDefault();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SomeClass value = cboName.SelectedValue as SomeClass;
if (value == null)
MessageBox.Show("No item is selected.");
else
MessageBox.Show("An item is selected.");
}
SomeClass empty = new SomeClass("");
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataContext = cboName.SelectedItem as SomeClass;
if (DataContext == null)
cboName.SelectedValue = DataContext = empty;
}
}
here one way to do it:
<StackPanel>
<ComboBox x:Name="cboName" ItemsSource="{Binding ComboBoxItems}" DisplayMemberPath="Name" SelectedItem="{Binding ComboBoxItems[0],Mode=OneTime}" IsEditable="True"/>
<DockPanel>
<Label>Selected Value</Label>
<TextBox Text="{Binding SelectedItem.Name,ElementName=cboName}"></TextBox>
</DockPanel>
<Button Click="Button_Click">Click Me</Button>
</StackPanel>
and the code behind :
public partial class MainWindow : Window
{
private ObservableCollection<SomeClass> _comboBoxItems;
public ObservableCollection<SomeClass> ComboBoxItems
{
get
{
return _comboBoxItems;
}
set
{
_comboBoxItems = value;
}
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
ComboBoxItems = new ObservableCollection<SomeClass>()
{
new SomeClass("First Name"),
new SomeClass("Second Name"),
new SomeClass("Third Name")
};
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (!ComboBoxItems.Any(x => x.Name == cboName.Text))
{
ComboBoxItems.Add(new SomeClass(cboName.Text));
}
}
}
public class SomeClass
{
public SomeClass(string name) { this.Name = name; }
public string Name { get; set; }
}
To better manage your objects you may consider
defining a new property For the Selected ComboBox Item (Of Type SomeClass), and bind it to the ComboBox SelectedItem,
Use ObservableCollection instead of just list and Implement the INotifyPropertyChanges Interface.

Update combobox while using DisplayMemberPath

I am using WPF and MVVM light framework (and I am new in using them)
Here is the situation:
I have a combobox displaying a list of items (loaded from a database) and I am using the DisplayMemberPath to display the title of the items in the combobox.
In the same GUI, the user can modify the item title in a text box. A button 'Save' allows the user to save the data into the database.
What I want to do is when the user clicks 'Save', the item title in the combobox gets updated too and the new value is displayed at that time. However, I do not know how to do that...
Some details on my implementation:
MainWindow.xaml
<ComboBox ItemsSource="{Binding SourceData}" SelectedItem="{Binding SelectedSourceData,Mode=TwoWay}" DisplayMemberPath="Title" />
<TextBlock Text="{Binding SelectedDataInTextFormat}"/>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Closing += (s, e) => ViewModelLocator.Cleanup();
}
}
MainViewModel.xaml
public class MainViewModel:ViewModelBase
{
public ObservableCollection<Foo> SourceData{get;set;}
public Foo SelectedSourceData
{
get{return _selectedFoo;}
set{_selectedFoo=value; RaisePropertyChanged("SelectedSourceData"); }
}
public string SelectedDataInTextFormat
{
get{return _selectedDataInTextFormat;}
set{_selectedDataInTextFormat=value; RaisePropertyChanged("SelectedDataInTextFormat");
}
}
I would appreciate if anyone could help me on this one.
Thanks for your help.
Romain
You might simply update the SelectedSourceData property when SelectedDataInTextFormat changes:
public string SelectedDataInTextFormat
{
get { return _selectedDataInTextFormat; }
set
{
_selectedDataInTextFormat = value;
RaisePropertyChanged("SelectedDataInTextFormat");
SelectedSourceData = SourceData.FirstOrDefault(f => f.Title == _selectedDataInTextFormat)
}
}
EDIT: In order to change the Title property of the currently selected Foo item in the ComboBox, you could implement INotifyPropertyChanged in your Foo class:
public class Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string title = string.Empty;
public string Title
{
get { return title; }
set
{
title = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Title"));
}
}
}
}
Then simply set the Title property of the selected item:
SelectedSourceData.Title = SelectedDataInTextFormat;
There is many ways to do this, This example takes advantage of the Button Tag property to send some data to the save button handler(or ICommand), Then we can set the TextBox UpdateSourceTrigger to Explicit and call the update when the Button is clicked.
Example:
Xaml:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="105" Width="156" Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<StackPanel Name="stackPanel1">
<ComboBox x:Name="combo" ItemsSource="{Binding SourceData}" DisplayMemberPath="Title" SelectedIndex="0"/>
<TextBox x:Name="txtbox" Text="{Binding ElementName=combo, Path=SelectedItem.Title, UpdateSourceTrigger=Explicit}"/>
<Button Content="Save" Tag="{Binding ElementName=txtbox}" Click="Button_Click"/>
</StackPanel>
</Grid>
</Window>
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Foo> _sourceData = new ObservableCollection<Foo>();
public MainWindow()
{
InitializeComponent();
SourceData.Add(new Foo { Title = "Stack" });
SourceData.Add(new Foo { Title = "Overflow" });
}
public ObservableCollection<Foo> SourceData
{
get { return _sourceData; }
set { _sourceData = value; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var txtbx = (sender as Button).Tag as TextBox;
txtbx.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class Foo : INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set { _title = value; RaisePropertyChanged("Title"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Code:
public ObservableCollection<Foo> SourceData{get;set;}
public Foo SelectedSourceData
{
get{
return _selectedFoo;
}
set{
_selectedFoo=value;
RaisePropertyChanged("SelectedSourceData");
}
}
public string SelectedDataInTextFormat //Bind the text to the SelectedItem title
{
get{
return SelectedSourceData.Title
}
set{
SelectedSourceData.Title=value;
RaisePropertyChanged("SelectedDataInTextFormat");
}
}
XAML:
<ComboBox ItemsSource="{Binding SourceData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedSourceData,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Title" />
<TextBlock Text="{Binding SelectedDataInTextFormat, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>

How to enable/disable a button in WPF?

I've got a relatively simple WPF application. The idea is to present a list of items to the user; for each item there is a checkbox to select/deselect the item.
My code, simplified, looks a bit like this:
class Thing { /* ... */ };
public static int SelectedCount { get; set; }
public class SelectableThing
{
private bool _selected;
public bool Selected {
get { return _selected; }
set { _selected = value; if (value) { SelectedCount++; } else { SelectedCount--; } }
}
public Thing thing { get; set; }
};
private ObservableCollection<SelectableThing> _selectableThings;
public Collection<SelectableThing> { get { return _selectableThings; } }
<DataGrid ItemSource="{Binding Path=SelectableThings}">
<DataGridCheckBoxColumn Binding="{Binding Selected}"/>
<DataGridTextColumn Binding="{Binding Thing.name}"/>
</DataGrid>
<Button Content="{Binding Path=SelectedTestCount}" Click="someFunc" />
So the idea is that the button content should show the count of tests selected. This should be accomplished because whenever SelectableThing.Selected is set, it should increment/decrement the SelectedCount as appropriate.
However, as far as I can tell the behavior doesn't work. The button text displays "0", regardless of selecting/deselecting items in the list.
Any ideas?
This problem is a little hairy since you have multiple view-model classes involved. Here's a crack at the code to solve this. The only thing I'm missing is that the DataGrid doesn't seem to update your items until you leave the row.
XAML:
<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>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding Path=SelectableThings}"
Grid.Row="0" Margin="6">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="IsSelected"
Binding="{Binding IsSelected}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
</DataGrid.Columns>
</DataGrid>
<Button Content="{Binding Path=SelectedTestCount}"
Command="{Binding SaveCommand}"
Grid.Row="1" Width="75" Height="23"
HorizontalAlignment="Right" Margin="0,0,6,6"/>
</Grid>
</Window>
Thing class:
public class Thing : INotifyPropertyChanged
{
private readonly List<SelectableThing> selectableThings;
private DelegateCommand saveCommand;
public Thing(IEnumerable<SelectableThing> selectableThings)
{
this.selectableThings = new List<SelectableThing>(selectableThings);
this.SelectableThings =
new ObservableCollection<SelectableThing>(this.selectableThings);
this.SaveCommand = this.saveCommand = new DelegateCommand(
o => Save(),
o => SelectableThings.Any(t => t.IsSelected)
);
// Bind children to change event
foreach (var selectableThing in this.selectableThings)
{
selectableThing.PropertyChanged += SelectableThingChanged;
}
SelectableThings.CollectionChanged += SelectableThingsChanged;
}
public ObservableCollection<SelectableThing> SelectableThings
{
get;
private set;
}
public int SelectedTestCount
{
get { return SelectableThings.Where(t => t.IsSelected).Count(); }
}
public ICommand SaveCommand { get; private set; }
private void Save()
{
// Todo: Implement
}
private void SelectableThingChanged(object sender,
PropertyChangedEventArgs args)
{
if (args.PropertyName == "IsSelected")
{
RaisePropertyChanged("SelectedTestCount");
saveCommand.RaiseCanExecuteChanged();
}
}
private void SelectableThingsChanged(object sender,
NotifyCollectionChangedEventArgs e)
{
foreach (SelectableThing selectableThing in
e.OldItems ?? new List<SelectableThing>())
{
selectableThing.PropertyChanged -= SelectableThingChanged;
RaisePropertyChanged("SelectedTestCount");
}
foreach (SelectableThing selectableThing in
e.NewItems ?? new List<SelectableThing>())
{
selectableThing.PropertyChanged += SelectableThingChanged;
RaisePropertyChanged("SelectedTestCount");
}
}
public void RaisePropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
SelectableThing class:
public class SelectableThing : INotifyPropertyChanged
{
private string name;
private bool isSelected;
public SelectableThing(string name)
{
this.name = name;
}
public string Name
{
get { return name; }
set
{
name = value;
RaisePropertyChanged("Name");
}
}
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Original Answer:
Bind Command to an ICommand. Set your CanExecute on the ICommand to return false when your condition isn't satisified.
In the setter for that IsSelected property, when the value changes, raise the CanExecuteChanged event.
The Command binding on a Button automatically enables/disables the button based on the result of CanExecute.
For more information on how to do this, including an implementation of ICommand that you could use here, see this mini-MVVM tutorial I wrote up for another question.
To fill out the implementaiton of CanExecute, I'd use something like Linq's .Any method. Then you don't have to bother checking Count, and can terminate the loop early if you find that any item is checked.
For example:
this.SaveCommand = new DelegateCommand(Save, CanSave);
// ...
private void Save(object unusedArg)
{
// Todo: Implement
}
private bool CanSave(object unusedArg)
{
return SelectableThings.Any(t => t.IsSelected);
}
Or since it is short, use a lambda inline:
this.SaveCommand = new DelegateCommand(Save,
o => SelectableThings.Any(t => t.IsSelected)
);
Bind the Content of the button to a field in your viewmodel and fire the OnChanged method for that field every time another item is selected or unselected. Bind the IsEnabled to a boolean field in your view model and set it to true/false as appropriate to enable or disable the button.

Resources