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.
Related
I know that this had been answered multiple times before, and I've followed every possible guide.
It doesn't work.
Here is my code:
XAML
<Label Text="{Binding Path=StatusMessage, Mode=TwoWay}"
Margin="10,0,10,5"
VerticalOptions="End"/>
C#
private string statusMessage;
public string StatusMessage {
get { return statusMessage; }
set
{
statusMessage = value;
OnPropertyChanged(nameof(StatusMessage));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
The class extends INotifyPropertyChanged and to modify the label text I tried both
StatusMessage = "Status: ...";
and
Device.BeginInvokeOnMainThread(() => { StatusMessage = "Status: ...";});.
Nothing works.
Any idea how to fix this mess?
EDIT
Adding BindingContext = this; as suggested in the main helped.
Now it won't update the label from code called from a different thread, as follows
private void OnEnableUser(bool authenticated)
{
if (SynchronizationContext.Current != null)
{
[...]
} else
{
Device.BeginInvokeOnMainThread(() =>
{
OnEnableUser(authenticated);
});
}
}
Have you set the DataContext in the code behind?
this.DataContext = classWithStatusMessage;
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}"
there is probably a really simple reason why this isnt working but I've tried everything. I have a TextBlock with Text bound to a variable, the variable changes but the Text doesn't :
<TextBlock x:Name="modeLabel" Style="{StaticResource IndiTextBlock}" Height="23" TextWrapping="Wrap" Grid.Row="0" Text="{Binding ModeLabelText}" Margin="35,22,58,0"/>
The code that controls the text value is in a viewmodel:
public string ModeLabelText { get { return _modeLabeltext; } }
public ComboBoxItem SelectedMode { get { return _selectedMode; }
set
{
if (_selectedMode == value) return;
_selectedMode = value;
ToggleMode(null);
EvaluateScenario(null);
}
and
private void ToggleMode(object parameter)
{
if (_isBasicCalculation)
{
_modeLabeltext = "Target profit";
_isBasicCalculation = false;
}
else
{
_modeLabeltext = "Total to invest";
_isBasicCalculation = true;
}
}
Your class has to implement the INotifyPropertyChanged interface, and on changes of your variables, you should trigger the event
public class Model : INotifyPropertyChanged
{
public event EventHandler PropertyChanged; // event from INotifyPropertyChanged
protected void RaisePropertyChanged(string propertyName)
{
var local = PropertyChanged;
if (local != null)
{
local.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public void ToggleMode()
{
// ... your code ...
RaisePropertyChanged("ModelLabelText");
}
}
Thank you Nguyen Kien
private void ToggleMode(object parameter)
{
if (_isBasicCalculation)
{
_modeLabeltext = "Target profit";
OnPropertyChanged("ModeLabelText");
_isBasicCalculation = false;
}
else
{
_modeLabeltext = "Total to invest";
OnPropertyChanged("ModeLabelText");
_isBasicCalculation = true;
}
}
I am trying to set the name (textbox) value using WCF Service. I am hosting service in WPF application. I used the MVVM Model initially to set textbox value from the MainWindow.cs and it worked. But then I made some properties static in order to access the same through the service contract. It still seems to setting the property of Model attribute but not changing value in the text box. Can anyone please guide me?
Model.cs
public class Model : INotifyPropertyChanged
{
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);
MessageBox.Show(field.ToString());
return true;
}
// props
private static string testname;
public static string TestName
{
get { return testname; }
set {
Model m = new Model();
m.SetField(ref testname, value, "TestName");
}
}
}
WCF InameService.cs
public class nameService : InameService
{
public void setMyName(string name)
{
Model.TestName = name;
}
}
MainWindow.xaml
<Grid Name="GridName">
<TextBox Name="TextName" HorizontalAlignment="Left" Height="23" Margin="193,140,0,0" TextWrapping="Wrap" Text="{Binding TestName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120" />
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
ServiceHost host = new ServiceHost(typeof(nameService));
InitializeComponent();
host.Open();
Model s = new Model();
//this.DataContext = s.NameValue.TestName;
Model.TestName = "Alicia";
this.TextName.DataContext = s;
}
}
Thanks Nathan for help. Following is the answer:
I changed the ViewModel to Singleton Class and also instantiated the composite Model object while creating the instance.
`class ViewModel
{
private static volatile ViewModel instance;
private static object _mutex = new object();
private ViewModel() { }
private Model model;
public Model NameValue
{
get { return model; }
set { model = value; }
}
public static ViewModel Instance
{
get
{
if (instance == null)
{
lock (_mutex)
{
if (instance == null)
{
instance = new ViewModel();
instance.model = new Model();
}
}
}
return instance;
}
}
}`
then changed the MainWindow.xaml.cs
try
{
ViewModel s = ViewModel.Instance;
s.NameValue.TestName = "Alicia";
this.DataContext = s;
this.TextName.DataContext = s;
}
catch (Exception e)
{
MessageBox.Show("Error" + e.Message);
}
Similar changes was done in the Service Contract Class. I hope this will help some one trying to get the value in
Don't use static properties as you can't bind to them. Use a static object instead or pass the Model object to the service for example in the constructor and use that instance for updates.
public class nameService : InameService
{
private Model model;
public nameService(Model m)
{
model = m;
}
public void setMyName(string name)
{
model.TestName = name;
}
}
public class Model : INotifyPropertyChanged
{
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);
MessageBox.Show(field.ToString());
return true;
}
// props
private string testname;
public string TestName
{
get { return testname; }
set {
Model m = new Model();
m.SetField(ref testname, value, "TestName");
}
}
}
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));
}
}