I have a WPF application that has multiple comboboxes and buttons. I am learning the MVVM model with this application. The first combobox will display a list of database instances. This is done at the start of the application. This works fine.
There is a button object next to the database instances combobox. When the user clicks this button I need to get the contents of the database instance combobox and use it in a call to get all the databases in that instance. I am using a RelayCommand (ICommand) for the actions. The action for the button is getting setup correctly. I have a method SelectedDatabase in the DBInstance class but it is null when I click the button.
In the LoadDBInfo method below the selectedItem parameter is null.
Here is my XAML:
<ComboBox x:Name="cbxRLFDBInstances" ItemsSource="{Binding DBInstances}"
SelectedValue="{Binding SelectedDBInstance}" SelectedValuePath="value"
HorizontalAlignment="Left" Height="28" Margin="189,87,0,0" VerticalAlignment="Top"
Width="250" FontFamily="Arial" FontSize="14.667"
IsEditable="True"/>
<Button x:Name="btnRLFDBLoadDBInfo" Content="Load DB Info" Command="{Binding LoadDBInfoCommand}"
CommandParameter="{Binding SelectedDBInstance}" HorizontalAlignment="Left" Height="26" Margin="475,89,0,0" VerticalAlignment="Top"
Width="101" FontFamily="Arial" FontSize="14.667" Background="#FFE8F9FF"
ToolTip="Click here after choosing or typing in the datbase instance. This will populate the database list."/>
<ComboBox x:Name="cbxRLFDBName" HorizontalAlignment="Left" Height="28" Margin="189,132,0,0"
ItemsSource="{Binding DBDatabases}" SelectedValue="{Binding SelectedDBDatabase}"
SelectedValuePath="value" VerticalAlignment="Top" Width="250" FontFamily="Arial"
FontSize="14.667" IsEditable="True" IsReadOnly="True"
ToolTip="Once a database is choosen the table list will automatically be populated."/>
Here is my ViewModel:
namespace DatabaseTest.ViewModel
{
class RLFDatabaseTableViewModel
{
Utilities dbtUtilities = new Utilities();
public RelayCommand LoadDBInfoCommand
{
get;
set;
}
public RLFDatabaseTableViewModel()
{
LoadDBInstances();
LoadDBInfoCommand = new RelayCommand(LoadDBInfo);
}
#region Database Instance
public IList<DBInstance> DBInstances
{
get;
set;
}
public void LoadDBInstances()
{
IList<DBInstance> dbInstances = nList<DBInstance>();
DataTable dt = SmoApplication.EnumAvailableSqlServers(false);
dbInstances.Add(new DBInstance { DBInstanceName = "fal-conversion\\mun2012ci" });
dbInstances.Add(new DBInstance { DBInstanceName = "fal-conversion\\mun2014ci" });
if (dt.Rows.Count > 0)
{
foreach (DataRow dr in dt.Rows)
{
dbInstances.Add(new DBInstance { DBInstanceName = dr["Name"].ToString() });
}
}
DBInstances = dbInstances;
}
#endregion Database Instance
#region Database Names
public IList<DBDatabase> DBDatabases
{
get;
set;
}
public void LoadDBDatabases()
{
IList<DBDatabase> dbDatabases = new List<DBDatabase>();
dbDatabases.Add(new DBDatabase { DBDatabaseName = "DB - A" });
dbDatabases.Add(new DBDatabase { DBDatabaseName = "DB - B" });
DBDatabases = dbDatabases;
}
#endregion Database Names
#region Button Cammands
void LoadDBInfo(object selectedItem)
{
SqlConnection sqlConn = null;
IList<DBDatabase> dbDatabaseNames = new List<DBDatabase>();
// string selectedItem = dbInstances.
//Setting the PUBLIC property 'TestText', so PropertyChanged event is fired
if (selectedItem == null)
dbDatabaseNames = null;
else
{
SelectedDBInstance = selectedItem as DBInstance;
dbDatabaseNames = dbtUtilities.GetDBNames(sqlConn, _selectedDBInstance.ToString(),
_selectedDBDatabase.ToString());
}
DBDatabases = dbDatabaseNames;
}
#endregion Button Commands
}
Here is my Model:
namespace DatabaseTest.Model
{
public class RLFDatabaseTableModel { }
public class DBInstance : INotifyPropertyChanged
{
private string strDBInstance;
public override string ToString()
{
return strDBInstance;
}
public string DBInstanceName
{
get
{
return strDBInstance;
}
set
{
if (strDBInstance != value)
{
strDBInstance = value;
RaisePropertyChanged("DBInstanceName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class DBDatabase : INotifyPropertyChanged
{
private string strDBDatabase;
public override string ToString()
{
return strDBDatabase;
}
public string DBDatabaseName
{
get
{
return strDBDatabase;
}
set
{
if (strDBDatabase != value)
{
strDBDatabase = value;
RaisePropertyChanged("DBDatabaseName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
EDIT: This is my code to load the 2nd combobox, cbxRLFDBName, The DBDatabase has the values but the combobox is not loaded.
public void LoadDatabases(string strDBInstanceName)
{
string strQuery;
IList<DBDatabase> dbDatabases = new List<DBDatabase>();
SqlConnection sqlUtilDBConn = null;
try
{
if (sqlUtilDBConn != null)
{
sqlUtilDBConn.Close();
}
sqlUtilDBConn = dbtUtilities.LoginToDatabase(strDBInstanceName, "master");
strQuery = "select name from sys.databases order by 1";
using (SqlCommand sqlCmd = new SqlCommand(strQuery, sqlUtilDBConn))
{
SqlDataReader sqlDataRead = sqlCmd.ExecuteReader();
while (sqlDataRead.Read())
{
string strDBNme = sqlDataRead.GetString(0);
dbDatabases.Add(new DBDatabase { DBDatabaseName = strDBNme });
}
sqlDataRead.Close();
sqlCmd.Dispose();
}
}
catch (Exception exQuery)
{
string strMsg;
strMsg = "GetNumRows: Error, '" + exQuery.Message + "', has occurred.";
System.Windows.MessageBox.Show(strMsg);
}
DBDatabases = dbDatabases;
}
EDIT: I have removed some of the code that is not needed in the hopes that this will be easier to read. My issue is that combobox "cbxRLFDBInstances" with ItemsSource="{Binding DBInstances}" loads the combobox fine. I also have another combobox, "cbxRLFDBName" with ItemsSource="{Binding DBDatabases}". When I choose the appropriate database instance and click the Load DB Info button, LoadDatabases runs and I can see that DBDatabases has the information needed in it. However the combobox is not loaded and I do not have a failure. Why does one ItemsSource data binding work and the other does not? I believe I am setting the class correctly but it seems lo=ike the binding is not happening? What have I missed?
Your code look fine to me, except for the SelectedValuePath="value" on the ComboBoxes. SelectedValuePath specifies a property on the selected item that is to be bound to the SelectedValue. SelectedDBInstance is of type DBInstance and DBInstance class does not define a value property, so I'd say you just have to remove SelectedValuePath="value" from the ComboBoxes.
Edit:
You need your ViewModel to implement INotifyPropertyChanged:
class RLFDatabaseTableViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
// the rest of RLFDatabaseTableViewModel implementation ...
}
And then every time you change a property value inside ViewModel, you also need to call RaisePropertyChanged immediately after. For example:
DBDatabases = dbDatabaseNames;
RaisePropertyChanged("DBDatabases");
It is helpful to define your properties like so:
public string StringProperty
{
get { return this.stringProperty; }
set {
this.stringProperty = value;
this.RaisePropertyChanged("StringProperty");
}
}
private string stringProperty;
Then you can just write
this.StringProperty = "new value";
and the new value will be set and a change notification sent.
You have to send the notifications because the View (XAML) and ViewModel are different classes and the View has no way of knowing that a property on the ViewModel has changed. If ViewModel implements INotifyPropertyChanged, WPF will listen for property changes through the PropertyChanged event and update the View accordingly.
Have you tried to pass command parameter as selected izem from combobox, something like:
CommandParameter="{Binding SelectedItem,ElementName=yourComboBoxName}"
Related
I'm having troubles with my UI not updating when my properties change, even though I have INotifyPropertyChange applied. When i run the code the first time, it shows up correct and the UI is updated. While debbuging I can see the new values being set to the strings of the viewmodel and that the OnPropertChange event is fired, it just don't happen anything in the UI. The code below will be in order of events. As extra information, I use the same code to update the viewmodel both in the first and second time.
public partial class Transaktioner : Window
{
ViewModelCommon.ViewModel view = new ViewModelCommon.ViewModel();
private static List<ViewModelCommon.Items2> getAccountingRowsListEdited = new List<ViewModelCommon.Items2>();
{
DataContext = view;
InitializeComponent();
}
private async Task GetAccountinTransactionsAsync()
{
await Task.Run(() =>
{
getAccountingRowsList = client.GetAccountingTransactions(ftglist[index], 0, ref status).ToList();
foreach (var v in getAccountingRowsList)
{
getAccountingRowsListEdited.Add(new ViewModelCommon.Items2
{
itemName2 = v.ver.ToString(),
value2 = v.text,
vertyp = v.vtyp,
s2 = v.kto.ToString(),
s3 = v.trdat.ToString()
});
}
Task.Run(async () =>
{
await SearchAndDisplayResult();
});
});
}
private async Task SearchAndDisplayResult(int exclusion = 0)
{
await Task.Run(() =>
{
var verfikationer = getAccountingRowsListEdited.Where(u => u.vertyp != exclusion).Count(u => u.s2.ToString().Equals("0"));
view.VerifikationerTotal = verfikationer.ToString();
});
}
The ViewModel:
class ViewModelCommon
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName]string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected void Test(string sb)
{
Transaktioner tr = new Transaktioner("");
tr.ExcludeStringChanged(sb);
}
}
public class ViewModel : ViewModelBase
{
private string _verifikationerTotal;
public string VerifikationerTotal
{
get { return _verifikationerTotal; }
set
{
if (value != _verifikationerTotal)
{
_verifikationerTotal = value;
OnPropertyChanged("VerifikationerTotal");
}
}
}
private string _ExcludeString;
public string ExcludeString
{
get { return _ExcludeString; }
set
{
if (value != _ExcludeString)
{
_ExcludeString = value;
OnPropertyChanged("ExcludeString");
Test(ExcludeString);
}
}
}
}
The WPF:
<TextBox x:Name="TextBoxVerifikationerTotal" Text="{Binding VerifikationerTotal}" IsEnabled="False" HorizontalAlignment="Left" Height="23" Margin="583,182,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="99"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="837,10,0,0" TextWrapping="Wrap" Text="{Binding Path=ExcludeString, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" VerticalAlignment="Top" Width="286"/>
The code above works as expected.
In the UI there is an option to introduce the optional attribute to exclude values. Those are bound to the "ExludeString" this also works and fires the event passing it again to the SearchAndDisplayResult(int exclusion = 0) with the replaces value of the int being passed. While debugging I can see that the event can successfully find a new value and passing it to the ViewModel, but it doesn't update the UI.
Are there any thoughts on why the UI is not updated? Thank you in advance!
The code has been shortend to show the vitals
Answer for this case was the
ViewModelCommon.ViewModel view = new ViewModelCommon.ViewModel();
not being set to a private static while working with Tasks.
I have WPF application with Combobox and Button. After I enter value into textbox and press button, the value from textbox should appear in updated list of combobox. I am trying to achieve this with MVVM and binding to combobox. Here is part of code from ViewModel.
public class ViewModel:INotifyPropertyChanged
{
DomainLogic dl = new DomainLogic();
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<string> expenseCategories = new ObservableCollection<string>();
public ObservableCollection<string> ExpenseCategories
{
get
{
return expenseCategories;
}
set
{
expenseCategories = value;
OnPropertyChanged("ExpenseCategories");
}
}
public ViewModel()
{
expenseCategories = new ObservableCollection<string>(dl.GetExpenseCategories());
}
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
Also I am using EF to access DB and DomainLogic class has a method to list all Expense Categories.
Here is code-behind from window:
DomainLogic dl = new DomainLogic();
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrWhiteSpace(textBox.Text))
{
dl.CreateNewExpenseCategory(textBox.Text);
}
else
{
MessageBox.Show("Enter category!");
}
}
Here is also XAML:
<ComboBox x:Name="ExpCategory" HorizontalAlignment="Left" Margin="72,50,0,0" VerticalAlignment="Top" Width="130" ItemsSource="{Binding ExpenseCategories, UpdateSourceTrigger=PropertyChanged}" />
When I add the new category the combobox isn't updated. I'm new to the whole MVVM pattern and I think I'm missing something here.
//EDIT
public void CreateNewExpenseCategory(string name)
{
using (var context = new ExpenseEntities())
{
ExpenseCategory category = new ExpenseCategory() { CategoryName = name};
context.ExpenseCategory.Add(category);
context.SaveChanges();
}
}
The problem is that CollectionChanged event doesn't fire.
You are adding a new element inside your DataContext, but you aren't updating your local view of data.
Once you update the DataContext you should refresh your ObservableCollection or use Local.
Here how you could use Local:
public ViewModel()
{
expenseCategories = dl.GetExpenseCategories().Local;
}
So you can directly do:
expenseCategories.Add(new ExpenseCategory() {textBox.Text});
dl.GetContext().SaveChanges();
Or you have to update the ObservableCollection:
dl.CreateNewExpenseCategory(textBox.Text);
// Update your ViewModel ObservableCollection.
However i think that you should use a Command and not an Event so you can update the ObservableCollection directly inside the ViewModel.
Example:
using Prism.Commands;
//Other usings
public class ViewModel : INotifyPropertyChanged
{
// Your class methods and properties
public DelegateCommand<string> AddNewExpenseCategory
{
get
{
return new DelegateCommand<string>(Execute_AddNewExpenseCategory);
}
}
public void Execute_AddNewExpenseCategory(string param)
{
expenseCategories.Add(new ExpenseCategory() { param });
dl.GetContext().SaveChanges();
}
I have a button like this:
<Button Content="Gönder" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="932,23,0,0" Height="25" Command="{Binding Path=SetTeamList}" CommandParameter="{Binding ElementName=UrlBox, Path=Text}"/>
And at the VM, i have a method
public void SetTeamList(string Url)
{
//Some things here
}
The solution is WinForms app, so i set DataContext like this:
var view = new dTeamMapperForm();
view.DataContext = new TeamMappingVM();
elementHost1.Child = view;
Nothing happens when i click the button, no error or something. I put break point to SetTeamList method and it's not executing on button click.
Edit: I have changed the whole VM, now it looks like:
class TeamMappingVM : INotifyPropertyChanged
{
public ObservableCollection<Team> TeamList { get; set; }
public ICommand SetTeamsCommand { get; internal set; }
private string _url;
public string Url
{
get { return _url; }
set
{
_url = value;
NotifyPropertyChanged("Url");
}
}
public void SetTeamList()
{
var mapper = new TeamMapper();
TeamList = new ObservableCollection<Team>(mapper.MapTeams(Url));
}
public bool CanParseTeams()
{
return !string.IsNullOrEmpty(Url);
}
public TeamMappingVM()
{
SetTeamsCommand = new RelayCommand(SetTeamList, CanParseTeams);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
The Command-Property of a Button expects you to Bind to an Property of type ICommand.
In your Case you tried to Bind to a method, which does not work.
Since you edited you post i will just post this as the answer:
XAML:
<Button Content="Gönder" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="932,23,0,0" Height="25" Command="{Binding Path=SetTeamsCommand }" CommandParameter="{Binding ElementName=UrlBox, Path=Text}"/>
class TeamMappingVM : INotifyPropertyChanged
{
public ObservableCollection<Team> TeamList { get; set; }
public ICommand SetTeamsCommand { get; internal set; }
private string _url;
public string Url
{
get { return _url; }
set
{
_url = value;
NotifyPropertyChanged("Url");
}
}
public void SetTeamList()
{
var mapper = new TeamMapper();
TeamList = new ObservableCollection<Team>(mapper.MapTeams(Url));
}
public bool CanParseTeams()
{
return !string.IsNullOrEmpty(Url);
}
public TeamMappingVM()
{
SetTeamsCommand = new RelayCommand(SetTeamList, CanParseTeams);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
As a minor side note. Which was not asked. Since you are probably Using Databinding for your URL Textbox you don't need to pass it into the method via CommandParameter, Since the URL Property of your ViewMOdel represents this textbox. You want to try to seperate the view from the logic. This is a very small issue and might not have any effect, but it sort of is a bad habit to fall into.
As Xeun pointed out, a Command is not a method but an object implementing the ICommand interface. A Command implementation look like this:
class MyCommand: ICommand
{
public bool CanExecute(object parameter)
{
return true; // if your command is "enabled" otherwhise return false
}
public void Execute(object parameter)
{
// do something usefull
}
}
In this sample you should add an instance of MyCommand to your ViewModel an
bind to it.
Please notice usually you dont code commands this way.
A command usually interact with your ViewModel (ie it invokes Model methods) and inside MyCommand you have not references to the ViewModel hosting it.
(You could create a Command which hold a reference to its ViewModel, but...) Usually inside a ViewModel you use a Relay command or a Delegate command (which are basically the same thing).
Hi I am beginner using C# trying to produce a WPF(MVVM).
I have currently a TextBox & a ComboBox on a Window Form.
At the moment, I would like to arrange such that when user input an Access DB file path into the TextBox, the ComboBox will be automatically updated such that its available Items is the Tables Name in the MDB file. When user changed the MDB file path to another, ComboBox Items will be refreshed as well.
I have already prepared below Properties in the GUI's ViewModel.
...
public string MdbDir { get{;} set {; RaisePropertyChanged("MdbDir");} }
public List<string> MdbTblList { get{;} set{...; RaisePropertyChanged("MdbTblList");}}
...
I have already prepared below method in the Model.
...
public List<string> ReturnMdbTblList(string mdbDir)
{
List<string> mdbTblList = new List<string>();
oCat = new ADOX.Catalog();
oCat.ActiveConnection = oConn;
foreach (ADOX.Table oTable in oCat.Tables)
{
mdbTblList.Add(oTable.Name);
}
return mdbTblList;
}
...
I have already prepared below in View.xaml
...
<TextBox Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding MdbDir}" />
<ComboBox Grid.Column="1" Grid.Row="3" SelectedItem="{Binding Path=SelectedMdbTbl,Mode=TwoWay}" ItemsSource="{Binding MdbTblList}"/>
...
All I don't know is how to link the Model Method to ViewModel, and to make the ComboBox aware of MdbDir changed.
Any idea on what else to add the coding and at the same time minimize the amendment on the current piece of coding?
Thanks very much in advance :)
You can do that in two ways.
When ever you type the path in textBox and press tab, the Set part of the property MdbDir will be called. So you can call method like below. And in that method method you can fetch the details from the Model and update it to the UI.
public string MdbDir
{
get
{
;
} set
{
;
RaisePropertyChanged("MdbDir");
UpDateTheList()
}
}
Or you can have button on the UI and click of that can do the same thing. to Bind commands to buttons you can refer the below links
http://theprofessionalspoint.blogspot.in/2013/04/icommand-interface-and-relaycommand.html
http://www.codeproject.com/Articles/126249/MVVM-Pattern-in-WPF-A-Simple-Tutorial-for-Absolute
One more observation, if your new creating list every time then List is fine, but if your adding or removing something with already existing list then it'll not work for you, you have to use observablecollection instead of list
It is acceptable for your ViewModel to maintain a reference to your Model as the ViewModel can be thought of as a wrapper for your Model.
You could put a call to your Model method ReturnMdbTblList such as:
public string MdbDir
{
get
{
return this.mdbDir;
}
set
{
this.mdbDir = value;
RaisePropertyChanged("MdbDir");
this.MdbTblList = this.model.ReturnMdbTblList(value);
}
}
This is straight forward to implement and effective. My personal preference is not put anything inside the get and set methods of properties that do not directly affect the field it is accessing or notifying others it has changed. That is just my preference though, others may be happy to do so and I am not saying it is wrong.
I would use a DelegateCommand on the button to make the call to your ReturnMdbTdlList:
Model, ViewMode & DelegateCommand
public class MyViewModel : INotifyPropertyChanged
{
private readonly MyModel model;
private string mdbDir;
public string MdbDir
{
get
{
return this.mdbDir;
}
set
{
this.mdbDir = value;
RaisePropertyChanged("MdbDir");
}
}
private List<string> mdbTblList;
public List<string> MdbTblList
{
get
{
return this.mdbTblList;
}
set
{
this.mdbTblList = value;
RaisePropertyChanged("MdbTblList");
}
}
private DelegateCommand updateMdbTblListCommand;
public ICommand UpdateMdbTblListCommand
{
get
{
return this.updateMdbTblListCommand ??
(this.updateMdbTblListCommand = new DelegateCommand(this.UpdateMdbTblList));
}
}
public MyViewModel()
{
// This would idealy be injected via the constructor
this.model = new MyModel();
}
private void UpdateMdbTblList(object obj)
{
var param = obj as string;
this.MdbTblList = this.model.ReturnMdbTblList(param);
}
#region [ INotifyPropertyChanged ]
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
public class MyModel
{
public List<string> ReturnMdbTblList(string mdbDir)
{
// Do soemthing
return new List<string>();
}
}
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<object> execute,
Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this._canExecute == null || this._canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
XAML
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<TextBox Height="23" Margin="10" Width="200" Text="{Binding MdbDir}" />
<Button Content="Click Me" Width="100" Height="25" Margin="10" Command="{Binding Path=UpdateMdbTblListCommand}" CommandParameter="{Binding Path=MdbDir}" />
</StackPanel>
We bind the Command property of the Button to our UpdateMdbTblCommand in the MyViewModel, we also bind the CommandParameter property of the Button to the MdbDir property of MyViewModel. When the Button is pressed the UpdateMdbTblCommand is executed which in turn calls the UpdateMdbTbl passing along the value of MdbDir as an argument and subsequently updating the MdbTblList property of MyViewModel.
As I said the DelegateCommand would be my preferred method, however, it may be overkill when taking into consideration what you have to write to achieve what can be done in the former example.
I have a listbox which has items populated from a database. Now I want to update a listbox with a new string value each time I call the Add function.
I did this in 2 ways.
I added the new value to the database and updated the ViewModel class where the listbox is binded to. And this works fine. (see AddNewNameFirstWay method below)
I added the new value to the database, reloaded the values from the database and updated the ViewModel. But this doesn't work. (see AddNewNameSecondWay method below)
Here is my ViewModel code
public class ViewModel
{
private DBContext context = new DBContext("Data source=isostore:/names2.sdf");
private ObservableCollection<NameTable> nameCollection;
public ObservableCollection<NameTable> NameCollection
{
get
{
return nameCollection;
}
set
{
if (nameCollection != value)
{
nameCollection = value;
NotifyPropertyChanged();
}
}
}
public void AddNewNameSecondWay(string s)
{
NameTable t = new NameTable() { Name = s };
context.NameDatabaseTable.InsertOnSubmit(t);
context.SubmitChanges();
LoadFromDB();
}
public void AddNewNameFirstWay(string s)
{
NameTable t = new NameTable() { Name = s };
context.NameDatabaseTable.InsertOnSubmit(t);
context.SubmitChanges();
NameCollection.Add(t);
}
public void LoadFromDB()
{
var query = from i in context.NameDatabaseTable
select i;
NameCollection = new ObservableCollection<NameTable>(query);
}
private void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName]string propertyName = null)
{
var tmp = PropertyChanged;
if (tmp != null)
tmp(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Here is my XAML binding code
<ListBox ItemsSource="{Binding NameCollection, Mode=OneWay}" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name, Mode=OneWay}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It seems to me that second method doesn't work, because the ObservableCollection memory reference changes. If this is correct, how to update the binding properly?
Reason I use the second method is that, I want to make sure all the DB constraints stands true for the values I insert.
You need to notify about the changes in property. Try this way for Framework 4.0,
Mode=OneWayToSource,UpdateSourceTrigger=PropertyChanged
Hope it helps...
Call OnPropertyChanged when you want notification about changes in your collection:
OnPropertyChanged("NameCollection")
Your ViewModel:
public class ViewModel: INotifyPropertyChanged
{
private DBContext context = new DBContext("Data source=isostore:/names2.sdf");
private ObservableCollection<NameTable> nameCollection;
public ObservableCollection<NameTable> NameCollection
{
get
{
return nameCollection;
}
set
{
if (nameCollection != value)
{
nameCollection = value;
OnPropertyChanged("NameCollection");
}
}
}
public void AddNewNameSecondWay(string s)
{
// your code
OnPropertyChanged("NameCollection");
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}