wpf mvvm binding change textBlock text [duplicate] - c#

This question already has answers here:
using of INotifyPropertyChanged
(3 answers)
Closed 1 year ago.
I have text block that i want to chance from false to true by his binding property.
The property has change to true but the text of text box stay false.
How can I do this right.
Thank for the help.
<TextBlock x:Name="resBlock" Grid.Row="3" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Center" Width="250" Height="50" Text="{Binding Source={StaticResource Locator}, Path=Main.Result}" TextAlignment="Center" FontSize="30" />
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
LoginCommand = new RelayCommand(Login);
user = new User();
}
DataService service = new DataService();
public User user { get; set; }
public bool Result { get; set; }
public ICommand LoginCommand { get; }
public async void Login()
{
Result = await service.LoginAsync(user); // get True
}
}

To change the amount of control with the viewmodel, you must implement the INotifyPropertyChanged interface.
change MainViewModel to:
public class MainViewModel : ViewModelBase, INotifyPropertyChanged
{
public MainViewModel()
{
LoginCommand = new RelayCommand(Login);
user = new User();
}
DataService service = new DataService();
public User user { get; set; }
public ICommand LoginCommand { get; }
public async void Login()
{
Result = await service.LoginAsync(user); // get True
}
private bool result;
public bool Result
{
get { return result; }
set
{
result = value;
OnPropertyChange(nameof(Result));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Related

ListBox does not get updated, even if it's set to ObservableCollection

I use Prism. Data provider works correctly (SQLite here).
<ListBox ItemsSource="{Binding Path=CategoryList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
public class ListsViewModel : BindableBase
{
private readonly IDataProvider _dataProvider;
private readonly IRegionManager _regionManager;
public DelegateCommand ClickAddCategory { get; set; }
private string categoryName;
public string CategoryName
{
get { return categoryName; }
set { SetProperty(ref categoryName, value); }
}
private ObservableCollection<ExtraCategory> categoryList;
public ObservableCollection<ExtraCategory> CategoryList
{
get
{
if (categoryList == null) return _dataProvider.GetCategoryList();
else return categoryList;
}
set { SetProperty(ref categoryList, value); }
}
public ListsViewModel(IRegionManager regionManager, IDataProvider dataProvider)
{
_dataProvider = dataProvider;
_regionManager = regionManager;
ClickAddCategory = new DelegateCommand(ClickedAddCategory);
//MessageBox.Show("Hello from " + this.ToString());
}
private void ClickedAddCategory()
{
ExtraCategory newCategoryFromForm = new ExtraCategory(CategoryName);
CategoryList.Add(newCategoryFromForm);
_dataProvider.AddCategory(newCategoryFromForm);
}
}
If I change the line:
CategoryList.Add(newCategoryFromForm);
to
CategoryList = _dataProvider.GetCategoryList();
everything would work fine because code inside set {} will run but that's not a solution. I would really appreciate some help. Also I really don't want to break MVVM pattern.
You want to put an ObservableCollection in a property without setter. And you want the value to stay the same.
private ObservableCollection<ExtraCategory> categoryList;
public ObservableCollection<ExtraCategory> CategoryList
{
get
{
if (categoryList == null) return _dataProvider.GetCategoryList();
else return categoryList;
}
set { SetProperty(ref categoryList, value); }
}
This gives you a new ObservableCollection everytime you call CategoryList.get, i.e. CategoryList.Add(newCategoryFromForm); fetches a new list, adds the new item, then discards the list (returning a value from a property's getter does not magically set a backing field).
I'd do it this way:
public ObservableCollection<ExtraCategory> CategoryList { get; }
and in constructor:
CategoryList = _dataProvider.GetCategoryList();
use and install nuget package PropertyChange.Fody.
Your class would look like this:
public class ListsViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly IDataProvider _dataProvider;
private readonly IRegionManager _regionManager;
public DelegateCommand ClickAddCategory { get; set; }
public string CategoryName { get; set; }
public ObservableCollection<ExtraCategory> CategoryList { get; set; }
public ListsViewModel(IRegionManager regionManager, IDataProvider dataProvider)
{
_dataProvider = dataProvider;
_regionManager = regionManager;
ClickAddCategory = new DelegateCommand(ClickedAddCategory);
CategoryList = _dataProvider.GetCategoryList();
//MessageBox.Show("Hello from " + this.ToString());
}
private void ClickedAddCategory()
{
ExtraCategory newCategoryFromForm = new ExtraCategory(CategoryName);
CategoryList.Add(newCategoryFromForm);
_dataProvider.AddCategory(newCategoryFromForm);
}
}
I'm sure it will work perfectly
If what you want is to update the list use a method and bind a command to execute when you want to refresh

Pass Parameter for Dynamic Buttons - MVVM Light

The following code successfully creates two buttons dynamically, what I can not figure out is how to make the buttons open a different files when clicked.
What am I missing?
XAML:
<ItemsControl ItemsSource="{Binding DataButtons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ButtonName}"
Command="{Binding ButtonCommand}"
CommandParameter="{Binding FilePath}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ViewModel:
namespace DynamicControlsMvvmLight.ViewModel
{
public class MainViewModel : ViewModelBase
{
private readonly ObservableCollection<ButtonModel> _dataButtons = new ObservableCollection<ButtonModel>();
public ObservableCollection<ButtonModel> DataButtons { get { return _dataButtons; } }
private ICommand _buttonCommand;
public ICommand ButtonCommand
{
get {
if (_buttonCommand == null) {
_buttonCommand = new RelayCommand<object>(CommandExecute, CanCommandExecute);
}
return _buttonCommand;
}
}
public MainViewModel()
{
ButtonModel data1 = new ButtonModel("Button 1", ButtonCommand, "c:/Folder/File1.PDF");
ButtonModel data2 = new ButtonModel("Button 2", ButtonCommand, "c:/Folder/File2.PDF");
DataButtons.Add(data1);
DataButtons.Add(data2);
}
private void CommandExecute(object FilePath)
{
ButtonModel button = FilePath as ButtonModel;
System.Diagnostics.Process.Start(button.FilePath);
}
private bool CanCommandExecute(object FilePath)
{
Console.WriteLine("CanCommandExecute Method...");
return true;
}
}
}
Model:
namespace DynamicControlsMvvmLight.Model
{
public class ButtonModel
{
public string ButtonName { get; set; }
public ICommand ButtonCommand { get; set; }
public string FilePath { get; set; }
public ButtonModel(string buttonName, ICommand buttonCommand, string filePath)
{
ButtonName = buttonName;
ButtonCommand = buttonCommand;
FilePath = filePath;
}
}
}
ERROR
I get the following error when I click any of the buttons.
RelayCommand expects to receive CommandParameter which is a string in this case.
The code must look like:
public ICommand ButtonCommand
{
get
{
if (_buttonCommand == null)
{
_buttonCommand = new RelayCommand<string>(CommandExecute, CanCommandExecute);
}
return _buttonCommand;
}
}
and
private void CommandExecute(string filePath)
{
System.Diagnostics.Process.Start(filePath);
}

XAML bound property isn't working as expected

I have a Crew property, the property has several fields, few of which are Code and InvoiceAmount. The plus button is supposed to insert a new crew into an ObservableCollection of crews. Adding the first item works fine, however when the second item is inserted the first item's code changes to the second item and the second item has no visible code. How do I fix it so that a new crew is inserted every time I click the + button?
Starting UI:
After one item (a) has been added:
Second item (b) has been added:
Here's the ViewModel code:
public class MainPageViewModel : ViewModelBase
{
public MainPageViewModel()
{
AddCrewCommand = new CustomCommand(param => addCrew(), null);
Crews.CollectionChanged += new NotifyCollectionChangedEventHandler(Crews_Updated);
}
private void Crews_Updated(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("lvCrewList");
}
public Crew Crew { get; set; } = new Crew();
public ObservableCollection<Crew> Crews { get; private set; } = new ObservableCollection<Crew>();
public Crew SelectedCrew { get; set; }
public ICommand AddCrewCommand { get; private set; }
private void addCrew()
{
Crews.Add(Crew);
Crew = new Crew();
}
public ObservableCollection<string> SelectedWorkOrder { get; set; }
}
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Here's the XAML bit that assigns the Code field:
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" >
<Label Content="Crew" Width="55" Height="25" Margin="10,10,0,0"/>
<TextBox x:Name="txtCrew" Width="75" Height="25" Margin="0,10,10,0"
Text="{Binding Crew.Code, Mode=TwoWay}" />
<Button Content="+" Width="25" Height="25" Margin="0, 10, 0, 0" Command="{Binding AddCrewCommand}" />
</StackPanel>
Crew Class:
public class Crew
{
public string Code { get; set; }
public decimal InvoiceAmount { get; set; } = 0;
public Job Job { get; set; }
public override string ToString() => Code;
}
It is because you are not raising a PropertyChanged event for your Crew property, therefore the textbox is still bound to the previously added crew.
Change your MainPageViewModel.Crew property to the following:
public class MainPageViewModel : ViewModelBase
{
.............
private Crew _crew = new Crew();
public Crew Crew
{
get { return _crew; }
set
{
if (_crew == value) return;
_crew = value;
RaisePropertyChanged(nameof(Crew));
}
}
.......
}

c# ListView not updating when Property Changed

My UI is not updating when more data is added to the ObservableCollection. The console output says A first chance exception of type 'System.NullReferenceException' occurred. Should I be using Inotifycollectionchanged instead? Here is some of the code:
<ListView x:Name="ListView2" ItemsSource="{Binding Source={x:Static d:GrabUserConversationModel._Conversation}, UpdateSourceTrigger=PropertyChanged}" SelectionChanged="ListView1_SelectionChanged">
UserConversationModel.cs
public class UserConversationModel : INotifyPropertyChanged
{
public UserConversationModel()
{
}
public string Name
{ get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string Obj)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(Obj));
}
}
}
MainWindow.xaml.cs
public partial class MainWindow
{
static GrabUserConversationModel grabUserConversationModel;
public MainWindow()
{
InitializeComponent();
...
}
static void AddData()
{
grabUserConversationModel.Conversation.Add(new UserConversationModel { Name = "TestName" });
}
GrabUserConversationModel.cs
class GrabUserConversationModel
{
public static ObservableCollection<UserConversationModel> _Conversation = new ObservableCollection<UserConversationModel>();
public ObservableCollection<UserConversationModel> Conversation
{
get { return _Conversation; }
set { _Conversation = value; }
}
...
your property ObservableCollection<UserConversationModel> Conversation is not implementing the INotifyPropertyChanged
public ObservableCollection<UserConversationModel> Conversation
{
get { return _Conversation; }
set { _Conversation = value; OnPropertyChanged("Conversation");}
}

MVVM Bindings not working properly

Update
Managed to fix the selectedIndex problem. I'd forgotten to set SelectedItem as well and naturally that caused a few issues.
So at 9AM this morning we got our 24 hour assignment and I have hit a brick wall.
We're supposed to create a program that allows a supervisor to Add and delete Employees and add Working Sessions, total hours and total earnings. But I am having some problems succesfully implementing this following the MVVM-Pattern. For some reason my Bindings simply aren't working and the only Solution I can see is someone looking over my project and helping me troubleshoot it.
Here is my code - I'm very sorry about having to post the entire thing but given that I have no clue where the problem is I did not see any other options. :
EmployeeModel
[Serializable]
public class WorkSessions : ObservableCollection<WorkSessionModel>
{
public WorkSessions()
{
}
}
[Serializable]
public class WorkSessionModel : INotifyPropertyChanged
{
private DateTime _dateTime;
private string _id;
private double _hours;
public WorkSessionModel()
{
}
public DateTime DateTime
{
get { return _dateTime; }
set
{
_dateTime = value;
NotifyPropertyChanged("DateTime");
}
}
public string ID
{
get { return _id; }
set
{
_id = value;
NotifyPropertyChanged("ID");
}
}
public double Hours
{
get { return _hours; }
set
{
_hours = value;
NotifyPropertyChanged("Hours");
NotifyPropertyChanged("TotalHours");
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
WorkSessionModel
[Serializable]
public class WorkSessions : ObservableCollection<WorkSessionModel>
{
public WorkSessions()
{
}
}
[Serializable]
public class WorkSessionModel : INotifyPropertyChanged
{
private DateTime _dateTime;
private string _id;
private double _hours;
public WorkSessionModel()
{
}
public DateTime DateTime
{
get { return _dateTime; }
set
{
_dateTime = value;
NotifyPropertyChanged("DateTime");
}
}
public string ID
{
get { return _id; }
set
{
_id = value;
NotifyPropertyChanged("ID");
}
}
public double Hours
{
get { return _hours; }
set
{
_hours = value;
NotifyPropertyChanged("Hours");
NotifyPropertyChanged("TotalHours");
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
EmployeeViewModel
public class EmployeeViewModel : ViewModelBase
{
private Employees _employeesModel = new Employees();
public Employees EmployeesView = new Employees();
public ObservableCollection<WorkSessionModel> WorkSessions { get; set; }
private string _id = "0";
private string _name = "noname";
private double _wage = 0;
private int _totalhours = 0;
public string ID
{
get { return _id; }
set { _id = value; RaisePropertyChanged("ID"); }
}
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public double Wage
{
get { return _wage; }
set
{
_wage = value;
RaisePropertyChanged("Wage");
}
}
public int TotalHours
{
get { return _totalhours; }
set
{
_totalhours = value;
RaisePropertyChanged("TotalHours");
}
}
private EmployeeModel _selectedEmployee = new EmployeeModel();
public EmployeeModel SelectedEmployee
{
get { return _selectedEmployee; }
set
{
_selectedEmployee = value;
RaisePropertyChanged("SelectedEmployee");
}
}
private int _selectedEmployeeIndex;
public int SelectedEmployeeIndex
{
get { return _selectedEmployeeIndex; }
set
{
_selectedEmployeeIndex = value;
RaisePropertyChanged("SelectedEmployeeIndex");
}
}
#region RelayCommands
// Employee Relay Commands
public RelayCommand EmployeeAddNewCommand { set; get; }
public RelayCommand EmployeeDeleteCommand { set; get; }
public RelayCommand EmployeeNextCommand { set; get; }
public RelayCommand EmployeePrevCommand { set; get; }
public RelayCommand EmployeeTotalHoursCommand { get; set; }
#endregion
public EmployeeViewModel()
{
InitCommands();
}
private void InitCommands()
{
EmployeeAddNewCommand = new RelayCommand(EmployeeAddNewExecute, EmployeeAddNewCanExecute);
EmployeeDeleteCommand = new RelayCommand(EmployeeDeleteNewExecute, EmployeeDeleteCanExecute);
EmployeeNextCommand = new RelayCommand(EmployeeNextExecute, EmployeeNextCanExecute);
EmployeePrevCommand = new RelayCommand(EmployeePrevExecute, EmployeePrevCanExecute);
//EmployeeTotalHoursCommand = new RelayCommand(EmployeeTotalHoursExecute, EmployeeTotalHoursCanExecute);
}
//private void EmployeeTotalHoursExecute()
//{
// _selectedEmployee.TotalHours();
//}
//private bool EmployeeTotalHoursCanExecute()
//{
// return true;
//}
private void EmployeeAddNewExecute()
{
EmployeeModel newEmployee = new EmployeeModel();
EmployeesView.Add(newEmployee);
_employeesModel.Add(newEmployee);
SelectedEmployee = newEmployee;
}
private bool EmployeeAddNewCanExecute()
{
return true;
}
private void EmployeeDeleteNewExecute()
{
if (MessageBox.Show("You are about delete all submissions for Employee," + SelectedEmployee.Name + "(" + SelectedEmployee.ID +")\r\nAre you sure?", "This is a Warning!", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
_employeesModel.Remove(SelectedEmployee);
EmployeesView.Remove(SelectedEmployee);
}
}
private bool EmployeeDeleteCanExecute()
{
if (SelectedEmployee != null)
return true;
else return false;
}
private void EmployeeNextExecute()
{
SelectedEmployeeIndex++;
}
private bool EmployeeNextCanExecute()
{
if (SelectedEmployeeIndex < EmployeesView.Count - 1)
return true;
return false;
}
private void EmployeePrevExecute()
{
SelectedEmployeeIndex--;
}
private bool EmployeePrevCanExecute()
{
if (SelectedEmployeeIndex > 0)
return true;
return false;
}
}
View
public partial class MainWindow : Window
{
public EmployeeViewModel EmployeeViewModel = new EmployeeViewModel();
public MainWindow()
{
InitializeComponent();
menu_employee.DataContext = EmployeeViewModel;
sp_employees.DataContext = EmployeeViewModel;
datagrid_employees.ItemsSource = EmployeeViewModel.EmployeesView;
grid_selectedEmployee.DataContext = EmployeeViewModel.SelectedEmployee;
}
}
I can see a few problems with your code:
When the SelectedIndex is updated, SelectedItem remains the same and vice versa.
It looks like your data binding is out of order:
The DataContext property cascades down to every child of a certain dependency object.
The code in the MainWindow constructor should probably be replaced by:
this.DataContext = EmployeeViewModel;
Then in XAML set the rest of the properties using Data Binding. The problem in your situation is that the DataContext of the selectedemployee is only set once. This means if you select another employee, then it will not update.
An example for your SelectedEmployee grid:
<Grid Name="grid_selectedEmployee" DataContext="{Binding SelectedEmployee,
UpdateSourceTrigger=PropertyChanged}">...</Grid>
One of the biggest things I see is you are setting properties, not binding them.
For example,
datagrid_employees.ItemsSource = EmployeeViewModel.EmployeesView;
You are telling your DataGrid that it's ItemsSource should be that specific object. You need to bind it to that value so you are telling it to point to that property instead. This will make your UI correctly reflect what's in your ViewModel
The other huge red flag I see is your ViewModel referencing something called and EmployeeView which leads me to believe your View and ViewModel too closely tied together.
Your ViewModel should contain all your business logic and code, while the View is usually XAML and simply reflects the ViewModel in a user-friendly way.
The View and the ViewModel should never directly reference each other (I have had my View reference my ViewModel in some rare occasions, but never the other way around)
For example, an EmployeesViewModel might contain
ObservableCollection<Employee> Employees
Employee SelectedEmployee
ICommand AddEmployeeCommand
ICommand DeleteEmployeeCommand
while your View (XAML) might look like this:
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Add" Command="{Binding AddEmployeeCommand}" />
<Button Content="Delete" Command="{Binding DeleteEmployeeCommand}" />
</StackPanel>
<DataGrid ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee}">
... etc
</DataGrid>
<UniformGrid DataContext="{Binding SelectedEmployee}" Columns="2" Rows="4">
<TextBlock Text="ID" />
<TextBox Text="{Binding Id}" />
... etc
</UniformGrid >
</StackPanel>
And the only thing you should be setting is the DataContext of the entire Window. Usually I overwrite App.OnStartup() to start up my application:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var view = new MainWindow();
var vm = new EmployeesViewModel;
view.DataContext = vm;
view.Show();
}
}
Although I suppose in your case this would also work:
public MainWindow()
{
InitializeComponent();
this.DataContext = new EmployeesViewModel();
}

Categories