So I'm building a program that pulls Table and Field names out of a Microsoft Access database and puts them in to two different Listbox items. As a test for my binding I had both boxes setup identically (Just a simple copy/paste because I was getting ahead of myself) and I got the Table names to bind successfully but only using lbTables.DataContext = this;(I tried to use lbTables.SetBinding (ListBox.DataContextProperty, new Binding ("MDBtoCSV.MainWindow")); but it doesn't work for some reason). The DataContext doesn't seem to inherit from the window above it which from what I've read is what what it's supposed to do.
When I began trying to pin down the DataContext on lbFields in XAML I found an odd problem. if I define and initialize the collection at the same time, globally(as below), private ObservableCollection<CheckedListItem<Table>> _listTables = new ObservableCollection<CheckedListItem<Label>> (); both ListBox behave how I would expect them too.
But if I instead initialize the collection in my code and use lbTables.DataContext = this;, then only lbTables populates while lbFields remains blank. Is there a preferred method or less fragile method to explicitly defining the DataContext and ItemSource?
Below is my XAML:
<ListBox x:Name="lbFields" HorizontalAlignment="Left" Height="70" Margin="10,113,0,0" VerticalAlignment="Top" Width="240"
DataContext="{Binding ElementName=appMainWindow, Mode=OneWay}" ItemsSource="{Binding Path=ListTables, Mode=OneWay}">
</ListBox>
</Grid>
TL;DR: Why would DataContext not inherit from Window? Why doesn't ListBox.DataContext = this act the same as the XAML version?
Edit:
private ObservableCollection<CheckedListItem<Table>> _listTables;// = new ObservableCollection<CheckedListItem<Table>> ();
public ObservableCollection<CheckedListItem<Table>> ListTables
{
get { return _listTables; }
private set { _listTables = value;}
}
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
private string name;
public string Name { get; set; }
public CheckedListItem () { }
public CheckedListItem (T item, bool isChecked = false)
{ this.item = item;
this.isChecked = isChecked;}
public T Item
{ get { return item; }
set
{ item = value;
NotifyPropertyChanged("Item");}
}
public bool IsChecked
{ get { return isChecked; }
set
{ isChecked = value;
NotifyPropertyChanged ("IsChecked");}
}
public class Table
{ public string name { get; set; }
private ObservableCollection<string> _listFields = new ObservableCollection<string> (
new string[]{"null","null","null","null"});
public ObservableCollection<string> ListFields { get { return _listFields; } set { _listFields = value; } }
}
I highly recommend you take a look at the MVVM pattern example and use that for your WPF and bindings. It makes all of this a lot easier.
I would suggest three ViewModel classes: MainWindowViewModel, DatabaseTableViewModel and DatabaseTableColumnViewModel. As you might imagine, they are nested in each respective one with some properties of their own like so:
class MainWindowViewModel : ViewModelBase
{
// Make sure you implement INotifyPropertyChanged and invoke on each property!
public string Title { get; set; }
public ObservableCollection<DatabaseTableViewModel> Tables { get; set; }
public DatabaseTableViewModel SelectedTable { get; set; }
}
class DatabaseTableViewModel : ViewModelBase
{
public string TableName { get; set; }
public ObservableCollection<DatabaseTableColumnViewModel> Columns { get; set; }
}
class DatabaseTableColumnTableViewModel : ViewModelBase
{
public string ColumnName { get; set; }
public string ColumnType { get; set; }
public DatabaseTableColumnViewModel SelectedColumn { get; set; }
}
So your XAML bindings would look something like this:
<ListBox ItemsSource="{Binding Path=Tables}"
SelectedItem="{Binding Path=SelectedTable}">
</ListBox>
<ListBox ItemsSource="{Binding Path=SelectedTable.Columns}"
SelectedItem="{Binding Path=SelectedTable.SelectedColumn}">
</ListBox>
You will have to work a little bit with this to get multiple selection working but that's the general idea with bindings.
You should be setting your MainWindow.DataContext to an instance of MainWindowViewModel.
Everything I'm doing turned out to work just fine, what I was missing was the INotifyPropertyChanged in the MainWindow. I couldn't figure it out before, but then I found this which is something that I've been looking for for three months.
Related
I can see this question has been asked before but nothing seems to work for me.
I have a wpf desktop app.
i have this comboBox:
<ComboBox ItemsSource="{Binding Users, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Value.Login"
SelectedValue="{Binding SelectedManagerUser,
Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Value"
IsSynchronizedWithCurrentItem="True" />
The data source is a dictionary object:
public Dictionary<string,UserRecord> Users
{
get
{
//get data
}
set { _Users = value; RaisePropertyChanged(Constants.VM_Users); }
}
I add a new entry in my MVVM and update the data.
I then set the selected item in my mvvm:
private UserRecord _selectedManagerUser;
public UserRecord SelectedManagerUser
{
get
{
return _selectedManagerUser;
}
set
{
_selectedManagerUser = value;
RaisePropertyChanged("SelectedManagerUser");
}
}
SelectedManagerUser = Users[temp];
public class UserRecord : ViewModelBase
{
private int _Active;
private int _UserRecordId;
private string _UserRef;
private string _FName;
private string _SName;
private string _Login;
private string _Salt;
private int _IsAdmin;
private string _FullName;
private string _Branch;
private string _Position;
private string _Department;
public int Disabled { get { return _Active; } set { _Active = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Active); } }
public int UserRecordId { get { return _UserRecordId; } set { _UserRecordId = value; RaisePropertyChanged("UserRecordId"); } }
public string UserRef { get { return _UserRef; } set { _UserRef = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_UserRef); } }
public string FName { get { return _FName; } set { _FName = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_FName); } }
public string SName { get { return _SName; } set { _SName = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_SName); } }
public string Login { get { return _Login; } set { _Login = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Login); } }
public string Salt { get { return _Salt; } set { _Salt = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Salt); } }
public int IsAdmin { get { return _IsAdmin; } set { _IsAdmin = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_IsAdmin); } }
public string Branch { get { return _Branch; } set { _Branch = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Branch); } }
public string Position { get { return _Position; } set { _Position = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Position); } }
public string Department { get { return _Department; } set { _Department = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Department); } }
public string FullName { get { return FName + ", " + SName; } set { _FullName = value; RaisePropertyChanged(InformedWorkerCommon.Constants.VM_Fullname); } }
}
I know the new item has been added because -
I can see it int the dropdown
I set a breakpoint in my code and inspect.
The combo box just displays an empty value.
Anything else I can try?
thanks
Not sure what's going wrong on your side, but it might be helpful to look at a working solution.
XAML:
<ComboBox ItemsSource="{Binding Users}"
DisplayMemberPath="Value.Name"
SelectedValue="{Binding SelectedUser}"
SelectedValuePath="Value" />
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += WindowLoaded;
var vm = new ViewModel();
vm.Users.Add("u1", new UserRecord { Name = "User 1" });
vm.Users.Add("u2", new UserRecord { Name = "User 2" });
vm.Users.Add("u3", new UserRecord { Name = "User 3" });
DataContext = vm;
}
private void WindowLoaded(object sender, RoutedEventArgs e)
{
// make sure it works after DataContext was set
var vm = (ViewModel)DataContext;
vm.SelectedUser = vm.Users["u2"];
}
}
public class UserRecord
{
public string Name { get; set; }
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Dictionary<string, UserRecord> Users { get; }
= new Dictionary<string, UserRecord>();
private UserRecord selectedUser;
public UserRecord SelectedUser
{
get { return selectedUser; }
set
{
selectedUser = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(SelectedUser)));
}
}
}
Your SelectedManagerUser property should be changed to this. The SelectedManagerUser property is set with new value but you do not raise that event so the UI will not be updated.
private UserRecord _selectedManagerUser;
public UserRecord SelectedManagerUser
{
get
{
return _selectedManagerUser;
}
set
{
_selectedManagerUser = value;
RaisePropertyChanged("SelectedManagerUser");
}
}
Download Prism from Nuget and inherit your class from BindableBase.
After it use this:
private UserRecord selectedManagerUser;
public UserRecord SelectedManagerUser
{
get { return this.selectedManagerUser; }
set { this.SetProperty(ref this.selectedManagerUser, value); }
}
One of two things can cause this. First, it might be because you're not setting SelectedManagerUser to be an instance of UserRecord that is NOT in the Dictionary, or that Dictionaries still suck for databinding. Lemme cover them both.
When you work with ItemsSource and SelectedItem bindings, if you want SelectedItem changes to be reflected in the UI, you must set it to an instance that can be found within ItemsSource. The control, by default, will look for the item in the source that is referentially equal to the selected item. I'm 99% sure it will use IEquatable<T> instead of reference checking if your items implement it.
If that's not your problem, then it's because Dictionaries suck for databinding.
Dictionaries are TERRIBLE for databinding. Just awful. If you need a keyed collection and you want to bind against it, create a custom collection extending KeyedCollection. With some extra work TItem can implement INPC (make the key read only, tho) and the collection can implement INCC. Works great for binding. Why do I mention this? Read on...
Your problem is that, within the ComboBox, SelectedItem is actually of type KeyValuePair<string,UserRecord>, and NOT UserRecord. So the binding will NOT work. If you grab a copy of Snoop and examine the bindings at runtime, you'll see this.
The problem is that the control doesn't know jack squat about Dictionaries. All it knows is IEnumerable<T>. Dictionary<K,T> implements IEnumerable<KeyValuePair<K,T>>, so the control creates an item for each key value pair. SelectedItem is also a key value pair. So, when you bind that to a property of type UserRecord, yes it is able to use the SelectedValuePath to set the value properly, but it cannot (does not) [ninja edit: unless this behavior has changed over the past few years :/] iterate the enumerable in order to find the correct key value pair when you set the value in your view model.
If UserRecord's key value is a property within the type, then definitely create a KeyedCollection for it. KeyedCollection<Tkey,TItem> implements 'IEnumerable` so it works seamlessly with bindings. If not, wrap it in a proxy, or add the property.
And when I say "wrap it in a proxy", anybody who says "what, like a KeyValuePair?" I'm going to punch you through the internet. The proxy becomes the value you bind against. Don't waste your time with this SelectedValuePath nonsense. Work with the proxies directly. When you need your value, extract it at the last moment, not immediately after the binding executes.
Currently, I'm developing a simple task manager application to exercise my Windows 10 development skills, using the Universal Windows Application (UWP) template and the MVVM design pattern.
In my ViewModel (which implements INotifyPropertyChanged), I've got an ObservableCollection<BasicTask> which is databound to a ListView in XAML, where BasicTask is a simple class I developed.
My commands are instantiated within the ViewModel as properties, and are databound to the 'Command' property of (in this case, ) an AppBarButton UI element. The command's function is to call a method in the ViewModel, which is defined within the constructor of the command like so;
class DeleteTaskCommand : ICommand
{
public DeleteTaskCommand(MainViewModel viewModel)
{
ViewModel = viewModel;
}
public MainViewModel ViewModel { get; private set; }
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => (ViewModel.SelectedTask != BasicTask.Default) ? true : false;
public void Execute(object parameter) => ViewModel.DeleteTask();
}
The DeleteTask() method in the ViewModel affects the ObservableCollection<BasicTask>, which, as I stated before, is databound to a ListView and therefore (to my knowledge) should push the changes immediately to the ListView. And this is exactly what is doesn't do :(
I can confirm that the command calls the method in the ViewModel properly as I replaced the body of the DeleteTask() method with await new MessageDialog("").ShowAsync();, which worked.
I've tried this with many projects with absolutely no success so far. One apparent solution is here, however, Visual Studio notifies that the CommandManager class doesn't exist :/
UPDATE
The XAML of the ListView mentioned above is the following;
<ListView ItemsSource="{Binding TaskLists}"
SelectedIndex="{Binding SelectedTaskListIndex, Mode=TwoWay}"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>[...]</Grid>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
TaskLists is an ObservableCollection<TaskList> TaskLists, and the TaskList class is as follows;
class TaskList
{
public string Title { get; set; }
public string Description { get; set; }
public List<BasicTask> Items { get; set; }
public static TaskList Generate(string title, string description, List<BasicTask> items)
=> new TaskList() { Title = title, Description = description, Items = items };
}
A second ListView binds to the Items property of the selected TaskList within the TaskLists ObservableCollection, and it's the second ListView that does not get updated.
public int SelectedTaskListIndex
{
get { return selectedTaskListIndex; }
set
{
selectedTaskListIndex = value;
SelectedTaskList = (value != -1) ? TaskLists[value] : TaskList.Default;
RaisePropertyChanged(nameof(SelectedTaskListIndex));
}
}
public TaskList SelectedTaskList
{
get { return selectedTaskList; }
set
{
selectedTaskList = value;
RaisePropertyChanged(nameof(SelectedTaskList));
}
}
<ListView ItemsSource="{Binding SelectedTaskList.Items}"
Any and all help with this issue will be appreciated :)
In my ViewModel (which implements INotifyPropertyChanged), I've got an
ObservableCollection which is databound to a ListView in
XAML
class TaskList {
public string Title { get; set; }
public string Description { get; set; }
public List Items { get; set; } ... }
Actually, I noticed that you created a List property in TaskList, if you replace it with ObservableCollection and also notify the UI in the right way, the UI updating should works, here is my DeleteTask() method:
private void DeleteTask()
{
SelectedTaskList.Items.Remove(SelectedTask);
}
Both SelectedTaskList and SelectedTask has notified the UI timely.
By the way, to update/raise CanExecuteChanged, I used this solution provided by Jerry Nixon - MSFT in this case
public TaskList SelectedTaskList
{
get { return selectedTaskList; }
set
{
selectedTaskList = value;
RaisePropertyChanged(nameof(SelectedTaskList));
DeleteTaskCommand.RaiseCanExecuteChanged();
}
}
public BasicTask SelectedTask
{
get { return selectedTask; }
set
{
selectedTask = value;
RaisePropertyChanged(nameof(SelectedTask));
DeleteTaskCommand.RaiseCanExecuteChanged();
}
}
Please check my feasible sample on Github
I am new in WPF I have implemented INotifyPropertyChanged interface. I have one viewmodel containing the property "TeamMemberList". The control executes the setter part, changes the property value but the PropertyChanged event remains null.
Here is code:
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
ViewModel:(Which inherits the viewmodelbase)
Property is
public List<Employee> TeamMemberList
{
get
{
return _teamMemberList;
}
set
{
_teamMemberList = value;
NotifyPropertyChanged("TeamMemberList");
}
}
Binding
<ListBox Margin="10" ItemsSource="{Binding TeamMemberList, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" >
when new employee added to the DB, model reads it & creates List for all emplyee then the TeamMeberList property gets updated. This is updation method for TeamMemberList
var qryEmp = from employee in ClientModel.EmployeeList
where employee.ReportingManager == UserProfile.EmployeeId
select new Employee
{
EmployeeId = employee.EmployeeId,
EmployeeName = employee.EmployeeName,
Designation = employee.Designation,
ProfilePic = employee.ProfilePic,
};
TeamMemberList = qryEmp.ToList();
And implementation of Employee
public class Employee : ViewModelBase
{
private string _employeeName;
private string _employeeId;
private string _profilePic;
private string _designation;
private string _reportinManager;
public string EmployeeName
{
get
{
return _employeeName;
}
set
{
_employeeName = value;
NotifyPropertyChanged("EmployeeName");
}
}
public string EmployeeId
{
get
{
return _employeeId;
}
set
{
_employeeId = value;
NotifyPropertyChanged("EmployeeId");
}
}
public string ProfilePic
{
get
{
return _profilePic;
}
set
{
_profilePic = value;
NotifyPropertyChanged("ProfilePic");
}
}
public string Designation
{
get
{
return _designation;
}
set
{
_designation = value;
NotifyPropertyChanged("Designation");
}
}
public string ReportingManager
{
get
{
return _reportinManager;
}
set
{
_reportinManager = value;
NotifyPropertyChanged("ReportingManager");
}
}
}
It's hard to say what the problem is when we don't see more code (eg: how you are setting the DataContext etc...).
But there is an easy way to debug your bindings by adding the following attribute to it:
<ListBox Margin="10" ItemsSource="{Binding TeamMemberList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PresentationTraceSources.TraceLevel=High>
Adding this attribute will output the whole binding sequence to the Output window of Visual Studio. That should point out what is going wrong.
If you want to enable this for all bindings, you can also use the Visual Studio options:
Use ObserveableCollection instead of a list
I've I really weird problem with my WPF / C# application. I've got a property which returns another property. Now I make a variable and set it to one of these properties. If I now change the value by binding, the variable is also changed.
To simplify it, here's the code:
Here's the first property:
public MainDataObject CmObj_Temp { get; set; }
Which is used here:
public MainDataObject CmObj_MainData {
get {
return TemporaryDataStore.CmObj_Temp;
}
set {
TemporaryDataStore.CmObj_Temp = value;
this.RaisePropertyChanged(() => this.CmObj_MainData);
}
}
From which I set a variable here:
CmObj_Backup = TemporaryDataStore.CmObj_Temp;
or also like this (makes no different):
CmObj_Backup = ((VM)this.DataContext).CmObj_MainData;
And also use for binding here:
<TextBox Text="{Binding CmObj_MainData.Str_Internnr, Mode=TwoWay}"/>
Now if I change the text of the Textbox it also changes it here:
CmObj_Backup.Str_Internnr);
Can someone tell my why?
How can I change that?
Thx
This is an smaller form of my code:
public class DataObject
{
public string Str_Test1 {get; set;}
public string Str_Test2 {get; set;}
// --> Much more properties
}
public static class TempData
{
public static DataObject DObj1 {get;set;}
}
public class ViewModel
{
public DataObject DObj2 {
get {
return TempData.DObj1;
}
set {
TempData.DataObjet.DObj1 = value;
this.RaisePropertyChanged(() => this.DObj2);
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
var VM = new ViewModel();
this.DataContext = VM;
}
public void SomeWhereInTheSoftware()
{
((ViewModel)this.DataContext).DObj2.Str_Test1 = "Before";
ObjBackup = ((ViewModel)this.DataContext).DObj2;
((ViewModel)this.DataContext).DObj2.Str_Test1 = "After";
// --> Here is ObjBackup.Str_Test1 also "After"!!
}
}
If you would show full code blocks instead of randomly chosen lines of code it would be easier to follow. Your example isnt very clear to me.
However, I think you are having an issue because you think you are have 2 copies of an object when you really have 1. Objects are kept as reference so if you create a MainObject and a CopyObject you cant just set CopyObject equal to MainObject and expect to have a real copy.
Again, I could be way off given I dont understand your question fully but for example:
class A {
public string Message { get; set; }
}
public static void Main()
{
A mainData = new A();
mainData.Message = "Main Data Message";
A backupData = mainData;
backupData.Message = "Backup Data Message";
Console.WriteLine(mainData.Message);
// Prints Backup Data Message
Console.WriteLine(backupData.Message);
// Prints Backup Data Message
}
Edit: cloning as a solution
As Viv mentioned in the comment the solution to your problem would be to clone the object, which creates an actual copy of the object as opposed to a reference to the object.
you would update class A in this way:
class A : ICloneable
{
public string Message { get; set; }
public override object Clone()
{
A clone = new A();
clone.Message = this.Message;
return clone;
}
}
reference to ICloneable is here http://msdn.microsoft.com/en-us/library/system.icloneable.aspx
In this class :
[Export(typeof(IScreen))]
public class BolleViewModel : Screen
{
....
}
i have this List :
public List<Article> List { get; private set; }
This list is the Binding of Datagrid to List :
<DataGrid HorizontalAlignment="Stretch" SelectedItem="{Binding SelectedArticle}"
Margin="14,41,12,61" VerticalAlignment="Stretch" AutoGenerateColumns="False" x:Name="List">
I want that when I call the method UPDATE , updates the values of the List and Datagrid.
This is my update method:
public void Update(List<Article> list)
{
List = list;
NotifyOfPropertyChange("List");
}
What i wrong ? ?
Caliburn.Micro doesn't support convention based binding for DataGrid out of the box, you can see this by checking the ConventionManager static constructor.
You can write your own convention using the ConventionManager, or you can just set the ItemsSource property binding instead in your view.
E.g.
<DataGrid ItemsSource="{Binding Articles}" SelectedItem="{Binding SelectedArticle}"
Margin="14,41,12,61" AutoGenerateColumns="False"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> ...
Other points:
List isn't a very good property name for your list of articles
Caliburn.Micro provides a lambda based override for NotifyOfPropertyChange which you should use to catch refactorings
A better pattern for implementing an INPC property is the following (this is because it's no longer the responsibility of the consumer who changes the property to invoke the PropertyChanged event)
Use:
private List<Article> articles;
public List<Article> Articles
{
get
{
return this.articles;
}
private set
{
if (this.articles == value)
{
return;
}
this.articles = value;
this.NotifyOfPropertyChange(() => this.Articles);
}
}
As this is a collection type, you should also ensure that you always return a collection rather than null. This prevents the need for consumers to check to avoid null reference exceptions.
This is viewModel of Datagrid's view:
[Export(typeof(IScreen))]
public class BViewModel : Screen
{
private List<Article> articles;
public List<Article> Articles
{
get
{
return this.articles;
}
private set
{
if (this.articles == value)
{
return;
}
this.articles = value;
this.NotifyOfPropertyChange(() => this.Articles);
}
}
public BolleViewModel()
{
Articles = recoverArticles(); //returns a list of articles
}
public void Update(List<Article> list)
{
Articles = list;
}
//is associated with a button
public void Detail()
{
if(SelectedArticle!=null)
WindowManager.ShowWindow(new DetailArticleViewModel(SelectedArticle, Articles), null, null);
else
{
MessageBox.Show("Select an article","Error!",MessageBoxButton.OK,MessageBoxImage.Error);
}
}
}
The DetailArticleViewModel change an item of Articles list and call Update method of BViewModel.
[Export(typeof(IScreen))]
public class DetailArticleViewModel : Screen
{
public List<Article > GeneralList;
public Article ArticleSelected;
public BViewModel bw;
public DetailArticleViewModel(Article art,List<Article> arts,BViewModel viewmodel)
{
ArticleSelected = art;
GeneralList = arts;
bw = viewmodel;
}
// is associated with a button
public void Save()
{
var index = GeneralList.FindIndex(item => item.Code.CompareTo(ArticleSelected.Code)==0);
GeneralList[index].Price = 900;
bw.Update(List);
}
}
But the price of Selected articles is not 900 ! Why ?