Bind Interface in WPF for ComboBox SelectedItem - c#

I've read all over the place, that binding is doable in WPF to Interfaces, but I'm having a heck of a time actually getting any traction with it. I'm using EF Core also, if it helps you ready my code. The ComboBox fills with data, so the bind of the data works, but SelectedItem fails to bind, and the text within the selected item shows blank.
I don't get how the following, binds to the object that implements the interface.
The XAML for ComboBox:
<ComboBox Height="23" x:Name="cbJumpList" Width="177" Margin="2" HorizontalAlignment="Left"
IsEditable="False"
DisplayMemberPath="Name"
SelectedItem="{Binding Path=(model:IData.SelectedJumpList), Mode=TwoWay}"
/>
MainWindow.xaml.cs:
protected IData DB { get; private set; }
public MainWindow()
{
InitializeComponent();
DB = new Data.DataSQLite(true);
DB.Bind_JumpLists_ItemsSource(cbJumpList);
}
IData.cs:
public interface IData : IDisposable, INotifyPropertyChanged
{
void Bind_JumpLists_ItemsSource(ItemsControl control);
IJumpList First_JumpList();
IJumpList SelectedJumpList { get; set; } // TwoWay Binding
}
IJumpList.cs
public interface IJumpList
{
long JumpListId { get; set; }
string Name { get; set; }
}
Then within the implemented object (Data.DataSQLite):
public void Bind_JumpLists_ItemsSource(ItemsControl control)
{
control.ItemsSource = null;
db.JumpLists.ToList();
control.ItemsSource = db.JumpLists.Local;
control.Tag = db.JumpLists.Local;
SelectedJumpList = db.JumpLists.FirstOrDefault();
}
public IJumpList SelectedJumpList
{
get { return _SelectedJumpList; }
set
{
_SelectedJumpList = value;
NotifyPropertyChanged();
}
}
IJumpList _SelectedJumpList;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
I should add, the PropertyChanged event remains null.

The SelectedItem property of a ComboBox is supposed to be bound to a property and not to a type. For the binding to work you should also set the DataContext of the ComboBox to an instance of the type where this property is defined.
Try this:
<ComboBox Height="23" x:Name="cbJumpList" Width="177" Margin="2" HorizontalAlignment="Left"
IsEditable="False"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedJumpList}" />
public void Bind_JumpLists_ItemsSource(ItemsControl control)
{
db.JumpLists.ToList();
control.DataContext = this;
control.ItemsSource = db.JumpLists.Local;
control.Tag = db.JumpLists.Local;
SelectedJumpList = db.JumpLists.FirstOrDefault();
}

Related

Change a Combobox's selectedIndex from ViewModel and when the Combobox is binded [duplicate]

This question already has answers here:
WPF - MVVM: ComboBox value after SelectionChanged
(2 answers)
Closed 8 months ago.
I want to have the selected index of a combobox change based on code from the ViewModel. Is this possible?
This is how my combobox is set up:
<ComboBox x:Name="cmbModels" DisplayMemberPath="ModelItemTextbox"
SelectedItem="ItemNameTextbox" SelectionChanged="ModelSelectionChange"
ItemsSource="{Binding ModelComboList}">
</ComboBox>
Something else, my bindings don't work unless I have the SelectedItem set to "ItemNameTextbox". The Combobox is binded to an observableCollection.
private ObservableCollection<ModelComboListModel> _modelcombolist = new ObservableCollection<ModelComboListModel>();
public ObservableCollection<ModelComboListModel> ModelComboList
{
get { return _modelcombolist; }
set
{
_modelcombolist = value;
OnPropertyChanged("ModelComboList");
}
}
And the class:
public class ModelComboListModel
{
public string ItemName { get; set; }
public string ItemId { get; set; }
//public override string ToString()
//{
// return $"ID:{ModelItemId} | {ModelItemName}";
//}
public string ItemTextbox
{
get
{
return $"{ ItemId }: {ItemName}";
}
}
}
The list just contains items and their id's.
Is there a good trick for changing the selectedindex from the ViewModel? I can't find anything useful on google or here :(
You probably want to bind to the SelectedIndex property in your ComboBox. Then you can be strategic with your gets and sets in the view model to get the behavior you are looking for. In this example I made a TextBox that can be used to change the index of the ComboBox:
<StackPanel>
<ComboBox x:Name="cmbModels"
SelectedItem="{Binding SelectedItem, Mode=OneWay}"
SelectedIndex="{Binding SelectedItemIndex}"
ItemsSource="{Binding ModelComboList}"
Margin="5">
</ComboBox>
<TextBox Text="{Binding ItemSelect}"
Margin="5"/>
</StackPanel>
View Model:
internal class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChangedEventHandler? handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private int _selectedItemIndex = 0;
public int SelectedItemIndex
{
get => _selectedItemIndex;
set
{
_selectedItemIndex = value;
OnPropertyChanged();
}
}
public ObservableCollection<string> ModelComboList { get; } = new() { "Item", "Foo", "Bar" };// populate with more items as needed
public string SelectedItem => ModelComboList[_selectedItemIndex];
public string ItemSelect
{
get => _selectedItemIndex.ToString();
set
{
if (int.Parse(value) < 0 || int.Parse(value) > ModelComboList.Count)
SelectedItemIndex = 0;
else
SelectedItemIndex = int.Parse(value);
}
}

Bind selected DataGrid row to textbox

I want to bind a textbox to a selected DataGrid. I have already binded the list to a datagrid but now I would like to bind the TextBoxtext to the DataGridselected row so its content will be placed into the TextBox
txtOccArea.DataContext = hegData;
//hegData is a list of an object
Thanks!
You should create a new class like this ( I hope you are using MVVM ).
public class YourViewVM : INotifyPropertyChanged
{
#region Fields
private object selectedDataGridCell;
private string textBoxContent;
private List<YourObject> dataGridSource;
#endregion
#region Properties
public object SelectedDataGridCell
{
get
{
return this.selectedDataGridCell;
}
set
{
if (this.selectedDataGridCell != value)
{
this.selectedDataGridCell = value;
OnPropertyChanged("SelectedDataGridCell");
}
}
}
public string TextBoxContent
{
get
{
return this.textBoxContent;
}
set
{
if (this.textBoxContent != value)
{
this.textBoxContent = value;
OnPropertyChanged("TextBoxContent");
}
}
}
public List<YourObject> DataGridSource
{
get
{
return this.dataGridSource;
}
set
{
if (this.dataGridSource != value)
{
this.dataGridSource = value;
OnPropertyChanged("Source");
}
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In your view, just modify it to:
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding DataGridSource}" SelectedItem="{Binding SelectedDataGridCell}" />
<TextBox Grid.Row="1" Text="{Binding TextBoxContent}"></TextBox>
</Grid>
You need to add the INotifyPropertyChanged so the TextBox knows when the selection has changed.
If you need to set the DataGridSource to your hegData list, just create a constructor and set the property there like this:
public YourViewVM(List<YourObject> hegData)
{
this.DataGridSource = hegData;
}
And where you create it just call it like:
YourViewVM yourViewVM = new YourViewVM(hegData)
If you just want to display the value in the TextBox it will be ok to bind in XAML. Try this:
<DataGrid x:Name="MyGrid" ItemsSource="{Binding hegData}"/>
<TextBox Text={Binding SelectedItem, ElementName=MyGrid}/>
If you actually need to alter the Selected Item, I think you should define a SelectedListItem property in your ViewModel and bind the TextBox's text to this property.
ViewModel:
public List<object> hegData {get;set;}
public object SelectedListItem {get;set;}
View:
<DataGrid ItemsSource="{Binding hegData}"
SelectedItem="{Binding SelectedListItem}"/>
<TextBox Text={Binding SelectedListItem}/>

WPF ComboBox validation error when binding to IEnumerable of Enum values

For the purpose of code reuse, I am attempting to bind a ComboBox ItemsSource to an enumerable of enum values defined in a viewmodel. (I am aware of the strategies for binding directly to the enum, but in order to achieve code reuse I need to bind to an enumerable.) On viewmodel construction, I set the selected item to the first value of the enumerable. When the UI first launches, however, the combobox loads with validation error:
Value '' could not be converted.
This error does not occur when I use the same XAML to bind to an enumerable of classes. After I select an enum value, I get no more validation errors and the UI works as intended. How do I avoid this error and get the combobox to display the selected item on startup?
The code details... I have a service implementing IAcquire<T> which returns an enumerable of enum values:
public interface IAcquire<T>
{
IReactiveList<T> Items { get; }
}
My viewmodel inheritance looks something like this:
class GranularitySelectionViewModel : ChartFilterSelectionBase<DataGranularity>
{
public GranularitySelectionViewModel([NotNull] IAcquire<DataGranularity> service)
: base(service, "Granularity")
{}
}
class ChartFilterSelectionBase<T> : SelectionViewModelBase
{
private readonly IAcquire<T> _service;
internal ChartFilterSelectionBase([NotNull] IAcquire<T> service, string label)
:base(label)
{
foreach (var value in service.Items)
{
Items.Add(value);
}
SelectedItem = Items.FirstOrDefault();
}
private readonly IReactiveList<T> _items = new ReactiveList<T>();
public new IReactiveList<T> Items
{
get { return _items; }
}
private T _selectedItem;
public new T SelectedItem
{
get { return _selectedItem; }
set { SetProperty(ref _selectedItem, value); }
}
}
public class SelectionBaseViewModel
{
protected SelectionBaseViewModel([NotNull] string label )
{
if (label == null) throw new ArgumentNullException("label");
_label = label;
}
private readonly string _label;
public string Label
{
get { return _label; }
}
//Placeholder to be overridden in derived class.
public object SelectedItem { get; set; }
//Placeholder to be overridden in derived class.
public IReactiveList<object> Items { get; private set; }
}
The XAML is as follows:
<DataTemplate DataType="{x:Type viewModels:SelectionBaseViewModel}">
<StackPanel Orientation="Vertical">
<Label Content="{Binding Label}" ContentStringFormat="{}{0}:" Margin="5,5,5,0"/>
<ComboBox Margin="5,0,5,5" ItemsSource="{Binding Items, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" BorderThickness="1" BorderBrush="White">
</ComboBox>
</StackPanel>
</DataTemplate>

WPF ComboBox ItemsSource not working

I am new to WPF, trying to fix up an existing program.
There is a ComboBox defined as:
<ComboBox Height="23" Margin="111.5,6.738,6,0" Name="comboBoxDocType" VerticalAlignment="Top" FontFamily="Calibri" FontSize="11" SelectedIndex="0" SelectionChanged="comboBoxDocType_SelectionChanged" ItemsSource="{Binding}">
Trying to populate it in the code behind:
DocumentTypesList = new List<string>();
DocumentTypesList.Add(DocumentTypes.Unknown);
DocumentTypesList.Add(DocumentTypes.PurchaseOrder);
DocumentTypesList.Add(DocumentTypes.RMInvoice);
DocumentTypesList.Add(DocumentTypes.SundryOne);
DocumentTypesList.Add(DocumentTypes.DevelopmentPaper);
comboBoxDocType.ItemsSource = DocumentTypesList;
ComboBox is coming up with nothing in it. Is there something missing?
ItemsSource="{Binding}" is unnecessary in this context.
This can be done by pure binding. I'm not quite sure what you try to achieve, but the example below shows how you can hook onto on an event and avoid codebehind. You could just bind to SelectedItem as well. However here is an suggestion for your problem.
Xaml:
<ComboBox Height="23" Margin="111.5,6.738,6,0" VerticalAlignment="Top" FontFamily="Calibri" FontSize="11" SelectedIndex="0" ItemsSource="{Binding DocumentTypesList}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<command:EventToCommand Command="{Binding DocumentSelectionChangedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Note that I am using a viewmodel here, so you must set datacontext of your view to an instance of this class. Personally I would have avoided the event if possible, and instead just bind to the selecteditem(s) and maybe create a behaviour, for avoiding this coupling.
Code:
public class YourViewModel : INotifyPropertyChanged
private ObservableCollection<DocumentTypes> documentTypesList = new ObservableCollection<DocumentTypes> {DocumentTypes.Unknown, DocumentTypes.PurchaseOrder, DocumentTypes.RMInvoice, DocumentTypes.SundryOne, DocumentTypes.DevelopmentPaper};
public ObservableCollection<DocumentTypes> DocumentTypesList
{
get { return documentTypesList; }
set
{
if (Equals(value, documentTypesList)) return;
documentTypesList = value;
OnPropertyChanged();
}
}
public ICommand DocumentSelectionChangedCommand { get; set; }
public YourViewModel()
{
InitStuff();
}
public void InitStuff(){
DocumentSelectionChangedCommand = new RelayCommand<SelectionChangedEventArgs>(OnDocumentChanged);
}
private void OnDocumentChanged(SelectionChangedEventArgs e)
{
// To your stuff here, but all this can be done by bindings as well!
// Invoke in some SelectedDocuments property's setter or something
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator] // Comment out this line if no R#
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Hope it helps,
Cheers,
Stian
public MainPage()
{
this.InitializeComponent();
//get menu
List<listboxitem> menu_list = Load_Menu();
lst_menu.ItemsSource = menu_list;
}
private NavigationHelper NavigationHelper;
private ObservableDictionary DefaultViewmodel = new ObservableDictionary();
private string[] Logo_menu_array = { "Assets/star-6-48.ico", "/Assets/note-48.ico", "/Assets/medal-48.ico", "/Assets/joystick-48.ico" };
private string[] Text_menu_array={"Phổ biến trên YouTuBe","Âm nhạc","Thể thao","Trò chơi"};
//Menu
public class listboxitem
{
public string textmenu { get; set; }
public string logomenu { get; set; }
}
//load menu
public List<listboxitem> Load_Menu()
{
List<listboxitem> text = new List<listboxitem>();
for (int i = 0; i < Math.Min(Logo_menu_array.Length, Text_menu_array.Length); i++)
{
var l = new listboxitem();
l.logomenu = Logo_menu_array[i];
l.textmenu = Text_menu_array[i];
text.Add(l);
}
return text;
}
I hope it will help you :)

Updating a MVVM View when model property is set to new instance

I have a ViewModel and a View in a WPF application. On the screen there are a selection of inputs (date picker, text box and combobox).
The inputs are bound to the NewItem property of the ViewModel, the DataGrid is bound to the WorkLog collection property.
When the user clicks on the Add button I want the NewItem to be added to the WorkLog collection, and the NewItem property reset in order to allow the user to add more items. The problem is that when I add the item, if I reinstantiate NewItem then the controls are still populated but in the background the VM values are all defaults (or nulls) so it doesn't work.
How can I reset the NewItem property and update the UI to reflect this? I tried INotifyPropertyChanged to no avail (as I am setting to new instance rather than changing values).
I have trimmed the code for brevity
Model
public class WorkLogItem : INotifyPropertyChanged
{
public WorkLogItem()
{
this.Datestamp = DateTime.Today;
this.Staff = new Lookup();
this.WorkItem = new Lookup();
}
#region ID
private Int32 _ID;
public Int32 ID
{
get { return this._ID; }
set
{
this._ID = value;
FirePropertyChanged("ID");
}
}
#endregion
#region Datestamp
private DateTime? _Datestamp;
public DateTime? Datestamp
{
get { return this._Datestamp; }
set
{
this._Datestamp = value;
FirePropertyChanged("Datestamp");
}
}
#endregion
#region Staff
private Model.Lookup _Staff;
public Model.Lookup Staff
{
get { return this._Staff; }
set
{
this._Staff = value;
FirePropertyChanged("Staff");
}
}
#endregion
#region WorkItem
private Model.Lookup _WorkItem;
public Model.Lookup WorkItem
{
get { return this._WorkItem; }
set
{
this._WorkItem = value;
FirePropertyChanged("WorkItem");
}
}
#endregion
#region Hours
private Decimal _Hours;
public Decimal Hours
{
get { return this._Hours; }
set
{
this._Hours = value;
FirePropertyChanged("Hours");
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void FirePropertyChanged(String name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(name));
}
}
View Model
public Model.WorkLogItem NewItem { get; set; }
public ObservableCollection<Model.WorkLogItem> WorkLog { get; set; }
View
<Label Content="Date " />
<DatePicker SelectedDate="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.NewItem.Datestamp, NotifyOnSourceUpdated=True}" />
<Label Content="Work Item " />
<ComboBox Grid.Column="1" Grid.Row="2" DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.WorkItems}" ItemsSource="{Binding}" DisplayMemberPath="Value" SelectedValuePath="ID" SelectedItem="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.WorkLogItem.Type, NotifyOnSourceUpdated=True}" IsSynchronizedWithCurrentItem="True" />
<Label Grid.Row="3" Content="Hours " />
<TextBox Grid.Column="1" Grid.Row="3" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.NewItem.Hours, NotifyOnSourceUpdated=True}" />
C#
In Window_Loaded:
this.DataContext = this.VM;
In Add_Click
this.VM.WorkLog.Add(this.VM.NewItem);
this.VM.NewItem = new Model.WorkLogItem();
Your ViewModel must also implement INotifyPropertyChanged
public class ViewModel : INotifyPropertyChanged
{
private Model.WorkLogItem _newItem;
public ViewModel()
{
NewItem = new Model.WorkLogItem();
WorkLog = new ObservableCollection<Model.WorkLogItem>();
}
public Model.WorkLogItem NewItem
{
get { return _newItem; }
set
{
_newItem = value;
FirePropertyChanged("NewItem");
}
}
public ObservableCollection<Model.WorkLogItem> WorkLog { get; set; }
// INotifyPropertyChanged implementation here...
}
When binding to your ComboBox, be sure to use Mode=TwoWay:
<ComboBox ... SelectedItem="{Binding ... Mode=TwoWay}" />
The way that I do this is to set up an empty constructor in my data object that sets all of its properties to their default values. Then when I want to 'reset' a view to all empty fields, I just set my data bound property to a new item:
NewItem = new YourDataType();
This updates all of the data bound control properties as you would expect. Please note that my data type classes all implement the INotifyPropertyChanged interface and that this method of clearing the UI controls will only work if they do implement it.

Categories