Combobox binding issue - c#

My ComboBox does not get populated with data.
Class Employee set to public, has variables such as:
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
Code on UserControl:
public IEnumerable<csEmployee> employeeList;
public ObservableCollection<csEmployee> _employeeSorted { get; set; }
public ucAddClient()
{
InitializeComponent();
//Establish connection
var GetMyData = new DataAccess();
//Get data by procedure
employeeList = GetMyDataPV.ExecuteStoredProc<csEmployee>("procedure", new {KeyDate = Key_to_extract});
employeeList = employeeList.Where(record => record.EmployeeLevelID > 300);
_employeeSorted = new ObservableCollection<csEmployee>(employeeList.Where(record => record != null));
}
And WPF:
<ComboBox x:Name="cbAddManager"
Foreground="#FF4D648B"
FontSize="12"
IsEditable="True"
ItemsSource="{Binding _employeeSorted}"
DisplayMemberPath="FirstName"
PreviewKeyDown="cbAddManager_PreviewKeyDown"
Width="200">
<!--<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width ="50" Text="{Binding LastName}"/>
<TextBlock Text=", "/>
<TextBlock Width ="50" Text="{Binding FirstName}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>-->
</ComboBox>
Do you have any idea, why ComboBoxis not populated? When I do this in code (I add it in user control class) it gets data needed.
Im not sure if Im binding it correctly?

That is because you assign a new instance of a collection to your _employeeSorted property after InitializeComponent. At that time, the binding is already set up and does not get notified that you have updated the property from null, because you do not implement INotifyPropertyChanged.
There are multiple ways to solve the issue:
Initialize the collection before InitializeComponent and work on this same collection if you intend to change it, using Clear and Add instead of creating a new instance on changes.
Implement the INotifyPropertyChanged interface and use it to notify changes to your property so that the bindings are updated the the changes are applied in the user interface, e.g.:
public partial class MyUserControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<csEmployee> _employeeSortedField;
public ObservableCollection<csEmployee> _employeeSorted
{
get => _employeeSortedField;
set
{
if (_employeeSortedField == value)
return;
_employeeSortedField = value;
OnPropertyChanged();
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Expose a depenedency property for the collection instead and bind it to a collection in your view model that is passed as data context of the UserControl, thus moving the data access out it and separating the view from the business logic and data (recommended, see below MVVM).
Another issue might be that you do not set your data context to the UserControl itself in XAML (which is not recommened by the way, although it might solve your issue). In this case, the binding is unable to resolve the property at runtime (a binding error will be shown in the output window).
<UserControl x:Class="YourProject.YourControl"
...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
As a note, it seems that you mix your business logic with your UserControl (view). Leverage the MVVM design pattern to create view models and seprate both concerns instead. Furthermore, if you set the data context of your UserControl to itself, you break data context inheritance.

Related

How to bind a datatype to a control in WinUI 3?

I have a datatype (model) I would like to display the data for in my UI by showing several properties using data binding.. It works in a GridView or ListView, but how do I do this when I only want a single model bound instead of a collection?
To do this with a collection, the following works in a ListView:
<ListView x:Name="MyListView"
ItemsSource="{x:Bind Shapes, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:Shape">
<StackPanel>
<TextBlock Text="{x:Bind Name}"></TextBlock>
<TextBlock Text="{x:Bind NumberOfSides}"></TextBlock>
<TextBlock Text="{x:Bind Color}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
On a page with a ObservableCollection of type Shape called Shapes:
public sealed partial class MyPage : Page
{
// ...
public ObservableCollection<Shape> Shapes { get; set; }
// ...
}
With the following model Shape:
public class Shape
{
public string Name { get; set; }
public string NumberOfSides { get; set; }
public string Color { get; set; }
}
I want to do something like this, but this does not work:
<Grid>
<StackPanel>
<TextBlock Text="{x:Bind Name}"></TextBlock>
<TextBlock Text="{x:Bind NumberOfSides}"></TextBlock>
<TextBlock Text="{x:Bind Color}"></TextBlock>
</StackPanel>
</Grid>
The data-binding is actually being done to the ListView, and the DataTemplate is simply declaring the layout to display the bound model with.
To accomplish this with a single bound item instead of a collection, you need to use a control that still has a template property. This is where the ContentControl comes in (Microsoft's official documentation). The ContentControl has a ContentTemplate property, which can contain a DataTemplate the same way a ListView or GridView can! You can then set the Content property of the ContentControl in the C# code, or bind to it (the same way you would bind to an ItemsSource property of a ListView or GridView, only with a single item instead of a collection).
The Simple Way
The following example works (Note that the DataTemplate and all of it's children are identical to how they would appear in a ListView or GridView):
<ContentControl x:Name="MyContentControl">
<ContentControl.ContentTemplate>
<DataTemplate x:DataType="models:Shape">
<StackPanel>
<TextBlock Text="{x:Bind Name}"></TextBlock>
<TextBlock Text="{x:Bind NumberOfSides}"></TextBlock>
<TextBlock Text="{x:Bind Color}"></TextBlock>
</StackPanel>
</DataTemplate>
<ContentControl.ContentTemplate>
</ContentControl>
Then in your C# code:
public sealed partial class MyPage : Page
{
// ...
public void SetShape(Shape shape)
{
this.MyContentControl.Content = shape;
}
// ...
}
The FULL Data Binding Way
You can also use data binding to bind to the shape property, but this will require some more work. Start by adding the binding to the ContentControl as follows:
<ContentControl x:Name="MyContentControl"
Content="{x:Bind MyShape}">
<ContentControl.ContentTemplate>
<!-- Contents all the same as before -->
<ContentControl.ContentTemplate>
</ContentControl>
And add the MyShape property to bind to on MyPage:
public sealed partial class MyPage : Page
{
// ...
public Shape MyShape { get; set; }
// ...
}
As is, this will not work. It may work when you set it initially, but if you change MyShape, the bound UI will not update.
Notice that if you were using ObservableCollection (such as in the ListView example), you can get the UI to update when you call Add() or Remove() functions of the ObservableCollection, but not when you change the ObservableCollection reference itself. The reason is that the ObservableCollection implements INotifyPropertyChanged which is what tells the bindings to update when you change the set of items in the collection. The following will not automatically work:
public sealed partial class MyPage : Page
{
// ...
public Shape MyShape { get; set; }
// ...
public void UpdateShape(Shape newShape)
{
this.MyShape = newShape;
}
}
To get this to work, you need to implement INotifyPropertyChanged on MyPage. This requires three steps (which may sound intimidating, but work the same way for any property):
Implement the interface INotifyPropertyChanged.
Add the PropertyChanged event.
Modify the MyShape setter to raise the PropertyChanged event.
Implement the interface INotifyPropertyChanged.
public sealed partial class MyPage : Page, INotifyPropertyChanged
{
// ...
}
Add the PropertyChanged event.
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise the PropertChanged event for the given property name.
/// </summary>
/// <param name="name">Name of the property changed.</param>
public void RaisePropertyChanged(string name)
{
// Ensure a handler is listening for the event.
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Modify the MyShape setter to raise the PropertyChanged event.
private Shape myShape;
public Shape MyShape
{
get => this.myShape;
set
{
this.myShape = value;
this.RaisePropertyChanged("MyShape");
}
}
Your final C# code will look like this:
public sealed partial class MyPage : Page, INotifyPropertyChanged
{
// ...
private Shape myShape;
public Shape MyShape
{
get => this.myShape;
set
{
this.myShape = value;
this.RaisePropertyChanged("MyShape");
}
}
// ...
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise the PropertChanged event for the given property name.
/// </summary>
/// <param name="name">Name of the property changed.</param>
public void RaisePropertyChanged(string name)
{
// Ensure a handler is listening for the event.
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
// ...
public void UpdateShape(Shape newShape)
{
this.MyShape = newShape;
}
}
NOW your ContentControl will work as expected with the different BindingMode values (OneTime, OneWay, and TwoWay).
If you want your bound controls WITHIN the ContentControl to update when you change a property of the shape, such as have the <TextBlock Text="{x:Bind Name}"> update when you do:
this.MyShape.Name = "A New Name";
You can similarly implement INotifyPropertyChanged on your Shape class itself with the same basic steps. This is the same whether you are using a ContentControl, GridView, ListView, or any other data-bound control. Basically, each layer you want to be able to update the properties of, and have a data bound UI update, you need to do this. This also needs to be done regardless of which of the two ways you used from this answer. You can refer to my answer here for details on this.

Adding/removing rows of controls

I have a panel and my idea is to have it populated by a stack panel containing two text boxes. When the user enters something in the left box, something should be generated in the right one, as follows.
<StackPanel Orientation="Horizontal">
<TextBox Name="Lefty" LostFocus="FillMyBuddy" />
<TextBox Name="Righty" LostFocus="FillMyBuddy" />
</StackPanel>
However, I'd like to add an option to add/remove rows and, since I wish not to limit myself to the number of such, I get a bit uncertain regarding the approach on two points.
Manipulating DOM (well, it's XAML/WPF but you see what I'm aiming at).
Event handling.
Is it a big no-no to programmatically affect the mark-up structure of the window? Or is it OK to add/remove panels during run-time?
What would the recommended way to be if I want the Lefty number 3 change stuff in Righty number 3? Anything more neat than checking the sender and pulling its siblings from the parent? I want to use a single event handler for any and all rows (knowing that the operations are always intra-row-wise).
You will want to follow MVVM, and have no code in your code-behind (programmatically affect the mark-up structure) files. The concept is easy when you grasp it, so learn it before you start writing your code.
In short, you are going to want to have a view model (something that implements INotifyPropertyChanged (INPC)) which holds your collection of items (which are going to be models, or view models in pure-MVVM). In "hybrid"-MVVM you could just have your models implement INPC.
Then, through the use of commands, you'd implement the logic to remove items from the list that its in. You can pass references, raise notification, using event bubbling, etc. (it's your preference) to have the item actually removed. In my case, I just passed a "manager" to the hybrid-model and held a reference to that. When the command is called (button is clicked), the model calls for the reference to remove itself from the list.
After you do that you define a DataTemplate to define what an "item" should look like one the View. You use a ItemsControl to show a collection of items, and bind to its ItemsSource so the collection of items are shown. Set your ItemsControl.ItemTemplate to the DataTemplate you created, and anything added to the collection bound to ItemsSource of the type defined in DataTemplate.DataType will render as you specify in the DataTemplate.
At the end of the day, you should learn about MVVM design, DataContext, INPC, Commands, Control types and their "main" properties, e.g. everything that inherits from ItemsControl has an ItemsSource property.
Here is a working example, where changing the original string, will reverse it and put it in the read-only right side text box:
MainWindow.xaml.cs (code-behind)
public partial class MainWindow : Window
{
StructureVm _struct = new StructureVm("Test");
public MainWindow()
{
InitializeComponent();
DataContext = _struct;
}
}
MainWindow.xaml (View)
<Window x:Class="DataTemplateWithCommands.MainWindow"
xmlns:local="clr-namespace:DataTemplateWithCommands"
Title="MainWindow" Height="350" Width="525" Background="Orange">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Model}"
x:Key="VmItem">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Original, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Encoded}"
IsReadOnly="True" />
<Button Content="X"
Command="{Binding RemoveMeCommand}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource VmItem}">
</ItemsControl>
</Grid>
</Window>
Interface (helpful for Dependency Injection)
public interface IStructureManager
{
bool RemoveItem(Model itemToRemove);
}
ViewModel
public class StructureVm : IStructureManager
{
private readonly ObservableCollection<Model> _items;
private readonly string _title;
public StructureVm(string title)
{
_title = title;
_items = new ObservableCollection<Model>
{
new Model(this, "12"),
new Model(this, "23"),
new Model(this, "34"),
new Model(this, "45"),
new Model(this, "56"),
new Model(this, "67"),
new Model(this, "78"),
new Model(this, "89"),
};
}}
public ObservableCollection<Model> Items
{
get
{
return _items;
}
}
public string Title
{
get
{
return _title;
}
}
public bool RemoveItem(Model itemToRemove)
{
return _items.Remove(itemToRemove);
}
}
Model (not pure-MVVM, pure MVVM models don't implement INPC, and don't have Command in them)
public class Model : INotifyPropertyChanged
{
private readonly RelayCommand _removeMe;
private string _original;
private string _encoded;
private readonly IStructureManager _manager;
public string Original
{
get
{
return _original;
}
set
{
_original = value;
Encoded = ReverseString(_original);
NotifyPropertyChanged();
}
}
public string Encoded
{
get
{
return _encoded;
}
set
{
_encoded = value;
NotifyPropertyChanged();
}
}
public ICommand RemoveMeCommand
{
get
{
return _removeMe;
}
}
public Model(IStructureManager manager, string original)
{
Original = original;
_manager = manager;
_removeMe = new RelayCommand(param => RemoveMe(), param => CanRemoveMe);
}
private void RemoveMe()
{
_manager.RemoveItem(this);
}
private bool CanRemoveMe
{
get
{
//Logic to enable/disable button
return true;
}
}
private string ReverseString(string s)
{
char[] arr = s.ToCharArray();
Array.Reverse(arr);
return new string(arr);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand implementation
From here on out all you have to do is change the attributes of your controls to whatever you're happy with and call it good. The example might be ugly, but I'm leaving it as an exercise for you to figure out other properties/attributes of WPF controls.

MVVM Binding Observable Collection to view?

In my application, I need to bind a checkbox list to an observable collection. I have seen many examples but I could not find a proper implementation for this and thats why I am posting this question.
The View:
<Grid Name="GrdMain" Background="White">
<ListView Name="lstConditions" VerticalAlignment="Top" Height="150"
ItemsSource="{Binding ConditionsModels}" Margin="0,25,0,0" BorderBrush="Transparent" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Path=condition}" Margin="8" Style="{StaticResource CheckBoxDefault}"
IsChecked="{Binding hasCondition,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
</grid>
The model:
public class ConditionsModel
{
public int profileId { get; set; }
public string condition { get; set; }
public bool hasCondition { get; set; }
}
The View Model:
public class ConditionsViewModel : INotifyPropertyChanged
{
private ConditionsModel _conditionsModel;
private ObservableCollection<ConditionsModel> _conditionsModels;
public ConditionsModel ConditionsModel
{
get
{
return _conditionsModel;
}
set
{
_conditionsModel = value;
RaisePropertyChanged("ConditionsModel");
}
}
public ObservableCollection<ConditionsModel> ConditionsModels
{
get
{
return _conditionsModels;
}
set
{
_conditionsModels = value;
RaisePropertyChanged("ConditionsModels");
}
}
public ConditionsViewModel(int profileId)
{
ConditionsModel = new ConditionsModel();
ConditionsModels = new ObservableCollection<ConditionsModel>();
ConditionsModels.CollectionChanged += ConditionsModels_CollectionChanged;
GetConditions(profileId);
}
void ConditionsModels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("ConditionsModels");
}
private void GetConditions(int profileId)
{
HealthAssessmentRepository _rep = new HealthAssessmentRepository();
_conditionsModels = _rep.GetConditions(profileId);
}
}
Is this a correct implementation? I need to update the model when the user checks or unchecks the checkbox. But its not raising the propery changed event when the check box is checked or unchecked.Should I implement the INotifyPropertyChanged interface on the model as well?
I have seen many examples, but all of them has different approaches to this and I am confused. Please show the correct implementation of this?
Thanks
I think you have missed the DataType property within DataTemplate. Just refer this
<DataTemplate DataType="{x:Type sampleApp:ConditionsModel}">
Here sampleApp in the namespace reference created within tag. And ConditionsModel is your model class.
You need to implement INotifyPropertyChanged for class ConditionsModel and raise PropertyChangedEvent for the property you want to observe/synchronize, because it is ViewModel as well.
For class ConditionsViewModel, it's the ViewModel of whole ListView, for ConditionsModel, it's the ViewModel of every line. ViewModel can be overlaid. If ConditionsModel is the domain model, my suggestion is that add a new ItemViewModel, because they belong to different layers. It's always better to distinguish the different layers properly.

Having trouble binding ViewModel to ComboBox

I have a viewmodel setup as the following
public class cDriveListVM
{
public string Drive { get; set; }
public cDriveListVM(string name)
{
Drive = name;
}
}
I declare the observablecollection in the window and set its datacontext to this observable collection.
public ObservableCollection<cDriveListVM> DriveList { get; set; }
private void dl()
{
DriveList = new ObservableCollection<cDriveListVM>();
DriveList.Add(new cDriveListVM("drive 1"));
DriveList.Add(new cDriveListVM("drive 2"));
this.DataContext = DriveList;
}
Xml for combobox:
<ComboBox x:Name="Drive_ComboBox" ItemsSource="{Binding Path=Drive}" HorizontalAlignment="Center" IsReadOnly="True" Grid.Column="0" Grid.Row="0" Width="300" Margin="10" SelectionChanged="Drive_Changed" Height="22" VerticalAlignment="Top"/>
I am just learning how to use Viewmodel so I am unsure what I am doing wrong, any help would be appreciated. I updated the xml file it results in the following combbox.
There are a few problems with this code.
One, the binding is set up wrong. Since the property with the viewmodel collection is DriveList, the binding should be ItemsSource="{Binding Path=DriveList}".
Two, you are attempting to display a field from your viewmodel, which is not doable. WPF's binding engine only works with properties, so the viewmodel should have a property:
public string Drive { get; set; }
And finally, the DisplayMemberPath should match the property name from the viewmodel: DisplayMemberPath="Drive".
Update: I just noticed that the DataContext is the observable collection itself -- I probably missed it on the first read. In that case, you want to bind directly to the data context:
ItemsSource="{Binding}"
And set DisplayMemberPath to the property you want to display:
DisplayMemberPath="Drive"

set SelectedIndex of Combobox in WPF to 0

I am binding a Collection at run time to a Combobox and I would like to set the Index after to 0. I could not find a straight answer to what I want.
_stationNames = new ObservableCollection<string>(_floorUnits.Unit.Select(f => f.Name));
_stationNames.Insert(0, "All");
stationsComboBox.ItemsSource = _stationNames;
stationsComboBox.SelectedIndex = 0;//Doesn;t work
Xaml
<ComboBox x:Name="stationsComboBox" Grid.Row="1" Grid.Column="1" Text="{Binding Name}"
SelectionChanged="StationComboBoxSelectionChanged" VerticalAlignment="Center" Margin="3"
SelectedIndex="0"/>
It sounds like you're trying to use it like you would with WinForms. WPF is a slightly different beast and a lot more powerful regarding bindings.
I recommend reading a bit on MVVM to get the most benefit from WPF. By binding the XAML to a view model class (rather than trying to wire things up in Code-behind) you will find you can accomplish what you want with a lot more flexibility without oodles of code.
For instance: Given the following VM:
public class MyViewModel: INotifyPropertyChanged
{
public ObservableCollection<string> StationNames
{
get;
private set;
}
public Something()
{
StationNames = new ObservableCollection<string>( new [] {_floorUnits.Unit.Select(f=>f.Name)});
StationNames.Insert(0, "All");
}
private string _selectedStationName = null;
public string SelectedStationName
{
get
{
return _selectedStationName;
}
set
{
_selectedStationName = value;
FirePropertyChanged("SelectedStationName");
}
}
private void FirePropertyChanged(string propertyName)
{
if ( PropertyChanged != null )
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You can set your view's (XAML form) DataContext to an instance of the ViewModel and update your combo box definition to:
<ComboBox x:Name="stationsComboBox" Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Path=StationNames}" SelectedItem={Binding Path=SelectedStationName} VerticalAlignment="Center" Margin="3"
SelectedIndex="0"/>
From here whenever the combo box selection changes, the VM's SelectedStationName updates to reflect the current selection, and from anywhere in the VM code, setting the VM's SelectedStationName will update the combo's selection. (I.e. implementing a Reset button, etc.)
Normally though, with something like what you've suggested, I would be looking at binding directly to the Units collection. (or VM's derived from units if they themselves can be viewed/edited.) In any case it should give you a bit of a starting point to start researching into WPF bindings.

Categories