Binding to WPF Combo Box - c#

I have a model class that I wish to bind a combo box to. My plan was to have an object with two propertied. 1) an ObservableCollection that contains the items I want to populate the combo box with. 2) A string property that stores the value of the selected item. I cannot seem to get this to work and open to suggestions. I am Trying to follow MVVM as best as possible. The behavior I observe is an empty combo box.
The class looks like this.
public class WellListGroup : Notifier
{
private ObservableCollection<string> _headers;
public ObservableCollection<string> headers
{
get { return this._headers; }
set { this._headers = value; OnPropertyChanged("headers"); }
}
private string _selected;
public string selected
{
get { return this._selected;}
set { this._selected = value; OnPropertyChanged("selected");}
}
}
Notifier looks like:
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
And my viewmodel makes a call to a data access layer that creates the following object i wish to bind to.
public class MainViewModel : Notifier
{
public static getWells gw = new getWells();
public static ObservableCollection<string> headers = gw.getHeaders();
public WellListGroup wlg = new WellListGroup {headers = headers, selected = null};
}
Data Access Layer - getHeaders()
public ObservableCollection<string> getHeaders()
{
ObservableCollection<string> vals = new ObservableCollection<string>();
WVWellModel wvm = new WVWellModel();
var properties = getProperties(wvm);
foreach (var p in properties)
{
string name = p.Name;
vals.Add(name);
}
return vals;
}
Then the view:
<ComboBox DockPanel.Dock="Top" ItemsSource = "{Binding Path = wlg.headers}" SelectedItem="{Binding Path = wlg.selected}"></ComboBox>
View Code Behind (Where the Data Context is set)
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
MainViewModel mvm = new MainViewModel();
DataContext = mvm;
}
}
App.xaml.cs
public partial class App : Application
{
private void OnStartup(object sender, StartupEventArgs e)
{
Views.MainView view = new Views.MainView();
view.Show();
}
private void APP_DispatcherUnhandledException(object sender,DispatcherUnhandledExceptionEventArgs e)
{
MessageBox.Show(e.Exception.Message);
e.Handled = true;
}
}
I have tried several iterations of this but cant for the life of me get this to work. I am presented with an empty combo box.

I am going to assume DataContext is set to MainViewModel on the view.
I think you well list group should call OnPropertyChanged
public class MainViewModel : Notifier
{
public static getWells gw = new getWells();
public static ObservableCollection<string> headers = gw.getHeaders();
private WellListGroup _wlg = new WellListGroup {headers = headers, selected = null};
public WellListGroup wlg
{
get { return _wlg; }
set { _wlg = value; OnPropertyChanged("wlg"); }
}
The combo box binding should look like this:
<ComboBox
ItemsSource = "{Binding wlg.headers}"
SelectedItem = "{Binding wlg.selected Mode=TwoWay}"
/>
If neither of those work I would make sure the MainViewModel is being instantiated and assigned to DataContext in the Page constructor or a page loaded event.
Here is a code project Tutorial that may help break down the binding process Step by Step WPF Data Binding with Comboboxes

Related

How to make a bound data member a default value in WPF ComboBox?

I have an ObservableCollection<string> named MyCollection containing "A", "B", "C", "D". I create a view like this:
<ComboBox x:Name="MyComboBox"
ItemsSource="{Binding MyCollection}"
SelectedIndex="{Binding 3}"/>
<Button Click="OnClickButton">Button</Button>
Then my codebehind looks like this:
public partial class MyClass {
private string _mySelection;
public string MySelection
{
get { return _mySelection; }
set
{
_mySelection = value;
}
}
public void OnClickButton(object sender, RoutedEventArgs e) {
MySelection = (MyComboBox.SelectedItem).ToString();
MessageBox.Show(MySelection);
}
This is fine. The ComboBox populates just as it should, and MySelection is set properly and appears in the message box. But currently, my ComboBox appears blank in the user interface until the user clicks on it and selects an option. I want option C to be the default value, and if the user doesn't select anything, then MySelection will be set to C.
But no matter how many different combinations of SelectedItem, SelectedValue, and SelectedIndex I try, I can't get it to work. The ComboBox always starts off empty.
How can I do this?
Set a default value of the _mySelection field, i.e. "C"
Or, more general, set the value of MySelection to the desired default value after construction.
Also make sure that the MySelection property fires a change notification.
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> MyCollection { get; }
= new ObservableCollection<string>();
private string mySelection;
public string MySelection
{
get { return mySelection; }
set
{
mySelection = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(MySelection)));
}
}
}
Initialize the DataContext of the view with an instance of MyViewModel:
public MainWindow()
{
InitializeComponent();
var vm = new MyViewModel();
vm.MyCollection.Add("A");
vm.MyCollection.Add("B");
vm.MyCollection.Add("C");
vm.MyCollection.Add("D");
vm.MySelection = "C";
DataContext = vm;
}
private void OnClickButton(object sender, RoutedEventArgs e)
{
MessageBox.Show(((MyViewModel)DataContext).MySelection);
}

Binding from external class in WPF

I have TextBlock binded manually in MainWindow.xaml
<TextBlock Name="TestPrice"
Height="30"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Text="{Binding Path=
ScreenMarketLogger, Mode=Default, UpdateSourceTrigger=PropertyChanged}"/>
In MainWindow.xaml.cs I define class with properties:
public class ScreenLoggerBind : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _ScreenMarketLogger;
public string ScreenMarketLogger
{
get
{
return _ScreenMarketLogger;
}
set
{
_ScreenMarketLogger = value;
OnPropertyChanged("ScreenMarketLogger");
}
}
private string _CurrentPrice;
public string CurrentPrice
{
get
{
return _CurrentPrice;
}
set
{
_CurrentPrice = value;
OnPropertyChanged("CurrentPrice");
}
}
private void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public ScreenLoggerBind()
{
this.ScreenMarketLogger = "\r\n begin \r\n";
}
}
I have another class (physically this is separate file) where I define constructor for ScreenLoggerBind class.
class ExternalClass
{
...
ScreenLoggerBind ScreenLogger = new ScreenLoggerBind();
...
}
Now I transfer DataContext into this class like this:
public void Init(MainWindow mw)
{
mw.TestPrice.DataContext = ScreenLogger;
}
And call this function in MainWindow.xaml.cs in the mainWindow method like this
ExternalClass ext = new ExternalClass()
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
ext.Init(this);
}
And if I assign a value to a variable ScreenLogger.ScreenMarketLogger I see result on main WPF form.
All works properly here.
Now question. If I create component dynamically in MainWindow.xaml.cs, like this for example:
Label lbl_Price = new Label();
lbl_Price.Name = string.Format("lbl_Price_{0}{1}", i.ToString(), cell.ToString());
Binding lbl_PriceBinding = new Binding("Content");
lbl_PriceBinding.Source = ScreenLogger.CurrentPrice;
lbl_PriceBinding.Mode = BindingMode.Default;
lbl_PriceBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
lbl_Price.SetBinding(Label.ContentProperty, lbl_PriceBinding);
....
And define DataContext in external class ExternalClass.cs
public void Init(MainWindow mw)
{
mw.TestPrice.DataContext = ScreenLogger;
foreach (Label lbl in mw.ChainGrid.Children.OfType<System.Windows.Controls.Label>())
{
if (lbl.Name == "lbl_XName_Price_00")
{
lbl.DataContext = ScreenLogger;
}
}
}
This is doesn't work! I see created dynamically Label on main form. But if I assign value to ScreenLogger.CurrentPrice variable I don't see any changes.
Why? where I made mistake?
Try to do as below:
Binding lbl_PriceBinding = new Binding("CurrentPrice");
lbl_PriceBinding.Source = ScreenLogger;
lbl_PriceBinding.Mode = BindingMode.OneWay;
You must provide the path to property of a source in Binding constructor. In your case the source is ScreenLogger and path, relative to it, is CurrentPrice.

Bind ViewModel List<T> to Listbox in C# Windows Universal App

I have a listbox which i want to get updated when the items get added to a list. I understand I need to bind the listbox. I was trying to follow this question/answer.
I have a ViewModel which handles the list:
namespace TESTS
{
public class ViewModel : INotifyPropertyChanged
{
private List<Cars> _listCars;
public List<Cars> listCars
{
get
{
return _listCars;
}
set
{
if (_listCars == value)
{
return;
}
this.RaisePropertyChanged("Message");
_listCars = value;
this.RaisePropertyChanged("Message");
}
}
public ViewModel()
{
listCars = new List<Cars>();
}
protected void RaisePropertyChanged(string propertyName)
{
Debug.WriteLine("Property Changed");
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Here is the class Cars:
public class Cars: INotifyPropertyChanged
{
public string model{ get; set; }
public string year{ get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
So I did the binding of listbox to the property path in my Viewmodel which is listCars.
<ListBox .... ItemsSource="{Binding listCars}">
So when in my Main.xaml.cs. I do a button click and add the item. It does not get added to the listbox even though its bind to the list on view model.
public sealed partial class MainPage : Page
{
public static ViewModel vm = new ViewModel();
public MainPage()
{
this.InitializeComponent();
this.DataContext = vm;
}
private void button_Click(object sender, RoutedEventArgs e)
{
Cars x = new Cars();
x.model = "Ford";
x.Year = "1998";
vm.listCars.Add(x);
}
}
I hope I explained what i implemented well enough. Is there something wrong in my implementation of ViewModel. I am new to MVVM. Please help.
Use ObservableCollection<T>, not List<T>. The former is designed to be used with MVVM, the latter is not. You'll get all your notifications automatically. It's doable with List<T>, but you'll have to write much more code and the performance will be much worse, especially with big collections. Just don't do it.
If you create the collection in the constructor, assign it to a read-only property and never change its instance (and this is the way you should do it), you don't even need to implement INPC.
When implementing INPC, you're expected to call RaisePropertyChanged after you've changed the property, once, and with the property name that has been changed, not a random unrelated string.

How do you do proper binding and updating of Xamarin.Forms ListView?

Using the MVVM pattern, I have a Model, ViewModel and View, which contains a ListView. The ListView is bound to a member of the ViewModel that is an ObservableCollection of the Model class. I can get the binding for the initial display to work and can update properties on the Model class for the appropriate row upon acting on the view, but I cannot get the view to refresh, pulling data from the Model class in the ObservableCollection. The ListView class does not contain method to invalidate or force a refresh, which would address my issue. How do I get the ListView to refresh after updating data on the Model?
Here is a simple example of what I am trying to do: Each row contains a Button and Label. Upon clicking a button, I can update the label, which will be reflected on screen. What I need to do is update the Model, which in turn should force an update of the view. However, I cannot get this to work. In an actual application, updating of the Model will be in a business logic layer, not in the view, after which I need to force refresh of the ListView.
Example Code:
using System;
using System.Collections.ObjectModel;
using Xamarin.Forms;
namespace ListViewTest
{
public class Model
{
public static int ids = 0;
public Model(string count)
{
Id = +1;
Count = count;
}
public int Id { get; set; }
public string Count { get; set; }
}
public class ModelList : ObservableCollection<Model>
{
}
public class ViewModel
{
ModelList list = new ModelList();
public ModelList ViewModelList
{
get { return list; }
set
{
list = value;
}
}
}
public partial class MainPage : ContentPage
{
public ViewModel viewModel;
public class DataCell : ViewCell
{
public DataCell()
{
var Btn = new Button();
Btn.Text = "Click";
var Data = new Label();
Data.SetBinding(Label.TextProperty,"Count");
Btn.Clicked += (object sender, EventArgs e) =>
{
Model model = (Model)(((Button)sender).Parent.BindingContext);
int count = Convert.ToInt32(model.Count);
count++;
model.Count = count.ToString();
// Need to refresh ListView from data source here... How???
};
StackLayout s = new StackLayout();
s.Orientation = StackOrientation.Horizontal;
s.Children.Add(Btn);
s.Children.Add(Data);
this.View = s;
}
}
public MainPage ()
{
viewModel = new ViewModel();
viewModel.ViewModelList.Add(new Model("0"));
viewModel.ViewModelList.Add(new Model("0"));
InitializeComponent();
}
public void InitializeComponent()
{
ListView listView = new ListView
{
ItemsSource = viewModel.ViewModelList,
ItemTemplate = new DataTemplate(() =>
{
return new DataCell();
})
};
Content = listView;
}
}
}
i think your model needs to implement INotifyPropertyChanged. So the UI knows that a value in model has changed
something like this :
public class Model : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
// props
private string _count;
public string Count
{
get { return _count; }
set { SetField(ref _count, value, "Count"); }
}
}
Remove the "set" method from your ViewModelList property. Whenever you use an ObservableCollection, just change the items in that one collection. Never replace it with another ObservableCollection.

ListBox ItemsSource Doesn't Update

I am facing a ListBox's ItemsSource related issue. I am implementing MVVM with WPF MVVM toolkit version 0.1.
I set one ListBox itemSource to update when a user double clicks on some other element (I handled the event in the code behind and executed the command there, since binding a command to specific events are not supported). At this point through the execution of the command a new ObservableCollection of items get generated and the ListBox's ItemsSource is intended to get updated accordingly. But it is not happening at the moment. ListBox does not update dynamically. What can be the problem? I am attaching relvent code for your reference.
XAML:
List of items which is doubled click to generate the next list:
<ListBox Height="162" HorizontalAlignment="Left" Margin="10,38,0,0" Name="tablesViewList" VerticalAlignment="Top" Width="144" Background="Transparent" BorderBrush="#20EEE2E2" BorderThickness="5" Foreground="White" ItemsSource="{Binding Path=Tables}" SelectedValue="{Binding TableNameSelected, Mode=OneWayToSource}" MouseDoubleClick="tablesViewList_MouseDoubleClick"/>
Second list of items which currently does not get updated:
<ListBox Height="153" HorizontalAlignment="Left" Margin="10,233,0,0" Name="columnList" VerticalAlignment="Top" Width="144" Background="Transparent" BorderBrush="#20EEE2E2" BorderThickness="5" Foreground="White" ItemsSource="{Binding Path=Columns, Mode=OneWay}" DisplayMemberPath="ColumnDiscriptor"></ListBox>
Code Behind:
private void tablesViewList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
MainViewModel currentViewModel = (MainViewModel)DataContext;
MessageBox.Show("Before event command is executed");
ICommand command = currentViewModel.PopulateColumns;
command.Execute(null);
MessageBox.Show(currentViewModel.TableNameSelected);
//command.Execute();
}
View Model:
namespace QueryBuilderMVVM.ViewModels
{
//delegate void Del();
public class MainViewModel : ViewModelBase
{
private DelegateCommand exitCommand;
#region Constructor
private ColumnsModel _columns;
public TablesModel Tables { get; set; }
public ControllersModel Operators { get; set; }
public ColumnsModel Columns {
get { return _columns; }
set {
_columns = value;
OnPropertyChanged("Columns");
}
}
public string TableNameSelected{get; set;}
public MainViewModel()
{
Tables = TablesModel.Current;
Operators = ControllersModel.Current;
Columns = ColumnsModel.ListOfColumns;
}
#endregion
public ICommand ExitCommand
{
get
{
if (exitCommand == null)
{
exitCommand = new DelegateCommand(Exit);
}
return exitCommand;
}
}
private void Exit()
{
Application.Current.Shutdown();
}
//Del columnsPopulateDelegate = new MainViewModel().GetColumns;
//Method to be assigned to the delegate
//Creates an object of type ColumnsModel
private void GetColumns() {
ColumnsModel.TableNameParam = TableNameSelected;
Columns = ColumnsModel.ListOfColumns;
}
private ICommand _PopulateColumns;
public ICommand PopulateColumns
{
get {
if (_PopulateColumns == null) {
_PopulateColumns = new DelegateCommand(GetColumns); // an action of type method is passed
}
return _PopulateColumns;
}
}
}
}
Model:
public class ColumnsModel : ObservableCollection<VisualQueryObject>
{
private DataSourceMetaDataRetriever dataSourceTableMetadataObject;// base object to retrieve sql data
private static ColumnsModel listOfColumns = null;
private static object _threadLock = new Object();
private static string tableNameParam = null;
public static string TableNameParam
{
get { return ColumnsModel.tableNameParam; }
set { ColumnsModel.tableNameParam = value; }
}
public static ColumnsModel ListOfColumns
{
get
{
lock (_threadLock)
if (tableNameParam != null)
listOfColumns = new ColumnsModel(tableNameParam);
return listOfColumns;
}
}
public ColumnsModel(string tableName)
{
ColumnsModel.tableNameParam = tableName;
Clear();
try
{
dataSourceTableMetadataObject = new DataSourceMetaDataRetriever();
List<ColumnDescriptionObject> columnsInTable = new List<ColumnDescriptionObject>();
columnsInTable = dataSourceTableMetadataObject.getDataTableSchema("Provider=SQLOLEDB;Data Source=.;Integrated Security=SSPI;Initial Catalog=LogiwizUser", ColumnsModel.tableNameParam);
//List<String> listOfTables = dataSourceTableMetadataObject.getDataBaseSchema("Provider=SQLOLEDB;Data Source=.;Integrated Security=SSPI;Initial Catalog=LogiwizUser");
//List<String> listOfTables = dsm.getDataBaseSchema("G:/mytestexcel.xlsx", true);
//ObservableCollection<VisualQueryObject> columnVisualQueryObjects = new ObservableCollection<VisualQueryObject>();
foreach (ColumnDescriptionObject columnDescription in columnsInTable)
{
VisualQueryObject columnVisual = new VisualQueryObject();
columnVisual.ColumnDiscriptor = columnDescription;
columnVisual.LabelType = "column";
Add(columnVisual);
}
}
catch (QueryBuilderException ex)
{
/* Label exceptionLabel = new Label();
exceptionLabel.Foreground = Brushes.White;
exceptionLabel.Content = ex.ExceptionMessage;
grid1.Children.Add(exceptionLabel);*/
}
}
}
Any help is greatly appreciated. Thanks in advance.
The setter of property Columns should raise a PropertyChanged event.
Implement INotifyPropertyChanged to do so : MSDN INotifyPropertyChanged
I guess MVVM Toolkit provides a way of doing so easily (perhaps ViewModelBase already implement the interface ...).
EDIT : Implementing INotifyPropertyChanged is not enough, you have to raise the event created by INotifyPropertyChanged. You property should look something like this :
private ColumnsModel _columns;
public ColumnsModel Columns
{
get { return _columns; }
set
{
_columns = value;
PropertyChanged("Columns");
}
}
use an observableCollection<T> instead of a List<T>
MSDN DOC:
WPF provides the ObservableCollection class, which is a built-in implementation of a data collection that exposes the INotifyCollectionChanged interface. Note that to fully support transferring data values from source objects to targets, each object in your collection that supports bindable properties must also implement the INotifyPropertyChanged interface. For more information, see Binding Sources Overview.

Categories

Resources