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));
}
}
.......
}
Related
I have this label like so:
<Label x:Name="QuestionText" FontSize="62" Text="{Binding question}" HorizontalTextAlignment="Center" Padding="0,20,0,0" />
This is inside a CarouselView:
<CarouselView
x:Name="Questions"
HeightRequest="475">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout HorizontalOptions="Center" Padding="10">
<Label x:Name="QuestionText" FontSize="62" Text="{Binding question}" HorizontalTextAlignment="Center" Padding="0,20,0,0" />
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
And I am populating the CarouselView like so:
protected override async void OnAppearing()
{
base.OnAppearing();
questions = await webService.GetTaskQuestions(taskcategory);
List<QuestionsClass> currentPage = new List<QuestionsClass>();
currentPage.Add(questions[currentPageIndex]);
Questions.ItemsSource = currentPage;
}
And I am trying to update my text like so:
questions[currentPageIndex].question = questions[currentPageIndex].question.Replace("_", questions[currentPageIndex].answer);
I have even tried:
Device.BeginInvokeOnMainThread(() =>
{
questions[currentPageIndex].question = questions[currentPageIndex].question.Replace("_", questions[currentPageIndex].answer);
});
And still nothing, here is my class:
public class QuestionsClass
{
public string question { get; set; }
public string answer { get; set; }
}
How do I update a label inside a CarouselView?
UPDATE
I have tried with INotifyPropertyChanged:
public class QuestionsClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string question { get; set; }
public string actualQuestion
{
get
{
return question;
}
set
{
question = value;
OnPropertyChanged();
}
}
public string answer { get; set; }
protected void OnPropertyChanged([CallerMemberName] string quetion = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(question));
}
}
Here is my label:
<Label FontSize="62" Text="{Binding actualQuestion}" HorizontalTextAlignment="Center" Padding="0,20,0,0" />
and here is how I am updating it:
questions[currentPageIndex].question = questions[currentPageIndex].question.Replace("_", questions[currentPageIndex].answer);
UPDATE
My class was wrong, this is the correct class and is now working:
public class QuestionsClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _question;
public string question {
get
{
return _question;
}
set
{
_question = value;
NotifyPropertyChanged("question");
}
}
public string answer { get; set; }
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
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));
}
}
I am trying to bind an ÒbservableCollection to a ComboBox using MVVM.
Here is the model:
public class ItemModel
{
public string Name{ get; set; }
}
ViewModel:
public class ItemsViewModel : INotifyPropertyChanged
{
public ObservableCollection<ItemModel> ObsItems{ get; set; }
public ItemsViewModel ()
{
List<string> items=MyDataTable.AsEnumerable().Select(row => row.Field<string>
("Id")).Distinct().ToList();
ObsItems= new ObservableCollection<ItemModel>();
foreach (var item in items)
{
ObsItems.Add(
new ItemModel
{
Name = item
}
);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
View:
<ComboBox HorizontalAlignment="Left" Margin="65,85,0,0" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding ObsItems}">
<ComboBox.DataContext>
<Models:ItemsViewModel />
</ComboBox.DataContext>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This code does not work when building from scratch. But it works during run time, when the view code (xaml) is modified. It stops working once the program is exited and is run again. What am I missing out?
You should use a readonly ObservableCollection property:
public class ItemsViewModel
{
public ObservableCollection<ItemModel> Items { get; }
= new ObservableCollection<ItemModel>();
public void InitializeItems()
{
Items.Clear();
foreach (var item in MyDataTable
.AsEnumerable()
.Select(row => row.Field<string>("Id"))
.Distinct()
.Select(name => new ItemModel { Name = name }))
{
Items.Add(item);
}
}
}
Is it possible to notify changes on a child class? Like the way binding on ValueB is notified when changing ValueA?
The PropertyChangedEventHandler only allows a propertyname to be notified.
The only way I see is adding functionality to the Child class to call notification there (Notify method)..
public class Parent: INotifyPropertyChanged
{
public Child ChildA
{
get; set;
}
public Child ChildB
{
get; set;
}
public int ValueA
{
get
{
return _valueA;
}
set
{
_valueA = value;
OnPropertyChanged(nameof(ValueA));
}
}
public int ValueB
{
get
{
return _valueB;
}
set
{
_valueB = value;
OnPropertyChanged(nameof(ValueA));
OnPropertyChanged(nameof(ValueB));
}
}
public void RefreshBindings()
{
OnPropertyChanged(ChildA.Check);
OnPropertyChanged(ChildB.Check);
}
}
public class Child: INotifyPropertyChanged
{
public void Notify(string property)
{
OnPropertyChanged(property);
}
public bool Check
{
get
{
return // something;
}
}
}
No, it's the source of the binding that should implement the INotifyPropertyChanged interface and raise change notifications for the framework to be able to refresh the bindings "automatically".
So if you bind to ChildA.Check of Parent, it's the object returned by the ChildA property (i.e. the Child class) that should implement INotifyPropertyChanged.
The other option would to bind to properties of Parent that wraps properties of Child, but the Child must still somehow notify the parent when its state changes.
#NawedNabiZada I appreciate you suggestions but they do not work.
Please only suggest it if you know for a fact they work.
Not sure what you tried, but my point is this:
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Child A :"/>
<Label Content="{Binding Path=ChildA.Check}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Child B :"/>
<Label Content="{Binding Path=ChildB.Check}"/>
</StackPanel>
<Button Content="Check/UnCheck" Command="{Binding Path=RefreshBindingCommand}"/>
</StackPanel>
</Grid>
Parent:
public class Parent : INotifyPropertyChanged
{
public Child ChildA
{
get; set;
}
public Child ChildB
{
get; set;
}
public ICommand RefreshBindingCommand { get; }
public Parent()
{
ChildA = new Child(true);
ChildB = new Child(false);
RefreshBindingCommand = new RelayCommand(RefreshBindingCommand_Execute);
}
void RefreshBindingCommand_Execute(object obj)
{
RefreshBindings();
}
public void RefreshBindings()
{
ChildA.Notify(nameof(ChildA.Check));
ChildB.Notify(nameof(ChildB.Check));
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Child:
public class Child : INotifyPropertyChanged
{
private bool _check;
public bool Check
{
get
{
_check = !_check;
return _check;
}
}
public Child(bool check)
{
_check = check;
}
public void Notify(string property)
{
OnPropertyChanged(property);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Proof that it works:
I am trying to bind my ViewModel to my ComboBox. I have ViewModel class defined like this:
class ViewModel
{
public ViewModel()
{
this.Car= "VW";
}
public string Car{ get; set; }
}
I set this ViewModel as DataContext in Window_Load like:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new CarModel();
}
Then in my xaml, I do this to bind my ComboBox to this ViewModel. I want to show the "VW" as selected by default in my ComboBox:
<ComboBox Name="cbCar" SelectedItem="{Binding Car, UpdateSourceTrigger=PropertyChanged}">
<ComboBoxItem Tag="Mazda">Mazda</ComboBoxItem>
<ComboBoxItem Tag="VW">VW</ComboBoxItem>
<ComboBoxItem Tag="Audi">Audi</ComboBoxItem>
</ComboBox>
I have 2 questions:
How do I set default value selected in Combo Box to "VW" (once form loads, it should show "VW" in combo box).
Instead of setting ComboBoxItems like above in xaml, how to I set it in my ViewModel and then load these in ComboBox?
Thanks,
UPDATE:
So far, I manage to implement this but I get error as below in the ViewModel c-tor:
namespace MyData
{
class ViewModel
{
public ViewModel()
{
this.Make = "";
this.Year = 1;
this.DefaultCar = "VW"; //this is where I get error 'No string allowed'
}
public IEnumerable<Car> Cars
{
get
{
var cars = new Car[] { new Car{Model="Mazda"}, new Car{Model="VW"}, new Car{Model="Audi"} };
DefaultCar = cars.FirstOrDefault(car => car.Model == "VW");
}
}
public string Make { get; set; }
public int Year { get; set; }
public Car DefaultCar { get; set; }
}
class Car
{
public string Model { get; set; }
}
}
As you are going to implement MVVM it will be a lot better if you start to think in objects to represent Cars in your application:
public class ViewModel
{
public Car SelectedCar{ get; set; }
public ObservableCollection<Car> Cars{
get {
var cars = new ObservableCollection(YOUR_DATA_STORE.Cars.ToList());
SelectedCar = cars.FirstOrDefault(car=>car.Model == "VW");
return cars;
}
}
}
public class Car
{
public string Model {get;set;}
public string Make { get; set; }
public int Year { get; set; }
}
Your Xaml:
<ComboBox SelectedItem="{Binding SelectedCar}", ItemsSource="{Binding Cars}"
UpdateSourceTrigger=PropertyChanged}"/>
Default Value:
If you set viewModel.Car = "VW", then it should auto-select that item in the combo box.
For this to work you will need to either implement INotifyPropertyChanged or set Car before you set DataContext.
INotifyPropertyChanged implementation might look like:
class ViewModel : INotifyPropertyChanged
{
private string car;
public ViewModel()
{
this.Car = "VW";
this.Cars = new ObservableCollection<string>() { "Mazda", "VW", "Audi" };
}
public string Car
{
get
{
return this.car;
}
set
{
if (this.car != value)
{
this.car = value;
OnPropertyChanged();
}
}
}
public ObservableCollection<string> Cars { get; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2.
Bind ItemsSource and SelectedItem.
<ComboBox ItemsSource="{Binding Cars}"
SelectedItem="{Binding Car, Mode=TwoWay}">
</ComboBox>
You can also set ComboBox.ItemTemplate if your source or view is more complex than just displaying a string:
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
In the view model just add a list property:
public ObservableCollection<string> Cars { get; set; }
It doesn't have to be ObservableCollection but that type will auto-update the UI whenever you change the collection.