WPF DataGrid binding doesn't update - c#

Here's my DataGrid:
<DataGrid x:Name="MoonMining"
ItemsSource="{Binding MarketData.MoonMinerals, ElementName=window}">
<DataGrid.DataContext>
<local:MoonMineral/>
</DataGrid.DataContext>
<DataGrid.Columns>
.. Yes i have columns and they are irrelevant to my question .
</DataGrid.Columns>
</DataGrid>
MarketData is a class which contains most of my programs logic. MoonMinerals is defined in that class:
public class MarketData
{
private ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
set { _moonMinerals = value; }
}
}
And here's my MoonMineral class:
[NotifyPropertyChanged]
public class MoonMineral
{
public MoonMineral()
: this("Def", "Def")
{
}
public MoonMineral(string name, string rarity)
{
Name = name;
Rarity = rarity;
}
public string Name { get; set; }
public double Price { get; set; }
public double Volume { get; set; }
public string Rarity { get; set; }
public double TransportVolume { get; set; }
public double TransportCosts { get; set; }
public double GrossProfit { get; set; }
public double NetProfit { get; set; }
}
As you can see, I'm using PostSharp to clear up my code, but when I manually implement INotifyPropertyChanged I have the same problem.
Now the problem is that my DataGrid doesn't update by itself, I have to manually call this in a method which modifies MoonMinerals:
var bindingExpression = MoonMining.GetBindingExpression(ItemsControl.ItemsSourceProperty);
if (bindingExpression != null)
bindingExpression.UpdateTarget();
I know this isn't big of a deal, but I wanted to finally manage to bind data to ui entirely using xaml. All my previous attempts involved setting DataGrids ItemsSource property every time I updated the data.

To sum up comments you're implementing INotifyPropertyChanged interface for MoonMineral class and use ObservableCollection which will handle changes to the collection but there seems to be nothing in place to handle changes to MoonMinerals property
private ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
set { _moonMinerals = value; }
}
You can either implement INotifyPropertyChanged interface in the class that exposes MoonMinerals property or change it to read-only and use only one instance of _moonMinerals and simply clear it and add/remove items
private readonly ObservableCollection<MoonMineral> _moonMinerals = new ObservableCollection<MoonMineral>();
public ObservableCollection<MoonMineral> MoonMinerals
{
get { return _moonMinerals; }
}
Also, as a side note, you don't need
<DataGrid.DataContext>
<local:MoonMineral/>
</DataGrid.DataContext>
as this will set DataContext of the DataGrid to new instance of MoonMineral. It works in your case as you change binding context of ItemsSource using ElementName so DataContext is not used in your case.

Related

MVVM WPF C# - Combobox binding foreign key FormatException

I have two entities: Client and Manager.
Code for them is:
public partial class Manager
{
public Manager()
{
this.Clients = new HashSet<Client>();
}
public int Id_manager { get; set; }
public string Name_manag { get; set; }
public virtual ICollection<Client> Clients { get; set; }
}
public partial class Client
{
public Client()
{}
public int Id_cl { get; set; }
public int Manager { get; set; }
public virtual Manager Manager1 { get; set; }
}
ViewModel class has this code:
private ObservableCollection<Manager> _managerList;
public ObservableCollection<Manager> ManagerList
{
get { return _managerList; }
set
{
_managerList = value;
OnPropertyChanged("ManagerList");
}
}
On my view i have combobox, where i can choose a client manager.
Also i have a DataGrid, which shows the list of clients.
I binded ComboBox with this XML code:
<ComboBox Name="cbManager"
Margin="5"
Padding="3"
ItemsSource="{Binding ManagerList}"
DisplayMemberPath="Name_manag"
SelectedValuePath="Id_manager"
SelectedValue="Binding Manager1.Id_manager"
SelectedItem="{Binding Path=Manager1, Mode=TwoWay}"/>
So the problem is - when i open my view, i get an exception :
System.Windows.Markup.XamlParseException:
System.Number.StringToNumber(string, System.Globalization.NumberStyles, ref System.Number.NumberBuffer, System.Globalization.NumberFormatInfo, bool)
System.Number.ParseInt32(string, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)
string.System.IConvertible.ToInt32(System.IFormatProvider)
System.Convert.ChangeType(object, System.Type, System.IFormatProvider)
MS.Internal.Data.SystemConvertConverter.Convert(object, System.Type, object, System.Globalization.CultureInfo)
MS.Internal.Data.DynamicValueConverter.Convert(object, System.Type, object, System.Globalization.CultureInfo)
System.Windows.Controls.Primitives.Selector.VerifyEqual(object, System.Type, object, MS.Internal.Data.DynamicValueConverter)
System.Windows.Controls.Primitives.Selector.FindItemWithValue(object, out int)
System.Windows.Controls.Primitives.Selector.SelectItemWithValue(object, bool)
System.Windows.Controls.Primitives.Selector.OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs)
I binded my ComboBox incorrectly, so help me to write correct bind.

ReactiveUI BindTo not updating the ReactiveList

I have a ReactiveObject class named GroupedIssueTypeSearchFilter:
public class GroupedIssueTypeSearchFilter : ReactiveObject
{
public int CategoryID { get; set; }
public string CategoryTitle { get; set; }
int selectionCode;
public int SelectionCode
{
get { return selectionCode; }
set { this.RaiseAndSetIfChanged(ref selectionCode, value); }
}
public ReactiveList<IssueTypeSearchFilter> IssueTypeFilterList { get; set; }
public GroupedIssueTypeSearchFilter(int catID, string catTitle, List<IssueTypeSearchFilter> issueTypeList)
{
this.CategoryID = catID;
this.CategoryTitle = catTitle;
this.IssueTypeFilterList = new ReactiveList<IssueTypeSearchFilter>(issueTypeList);
}
}
Then in IssueTypeFilterTableViewDelegate class, one of its properties is GroupedIssueTypesFilter:
public ReactiveList<GroupedIssueTypeSearchFilter> GroupedIssueTypesFilter { get; set; }
I also have a ViewController and its ViewModel. I used BindTo to bind one of the view model properties to that class:
IssueTypeFilterTableViewDelegate tvd = new IssueTypeFilterTableViewDelegate();
this.WhenAnyValue(vm => vm.ViewModel.GroupedStandardIssueTypesTV)
.BindTo(this, x => x.tvd.GroupedIssueTypesFilter);
GroupedStandardIssueTypesTV type is ReactiveList<GroupedIssueTypeSearchFilter>.
Later, with some operation inside the ViewModel, the GroupedStandardIssueTypesTV.IssueTypeFilterList value is changed.
But, GroupedIssueTypesFilter.IssueTypeFilterList value is not changed. I need to close the View, re-open it, then its value will be updated.
How to make the GroupedIssueTypesFilter follows the changes in GroupedStandardIssueTypesTV?
Fully agree with #ramonesteban78 answer.
But you can go in another way and refill your ReactiveList with Clear and Add/AddRange methods.
Like this:
public GroupedIssueTypeSearchFilter(int catID, string catTitle, List<IssueTypeSearchFilter> issueTypeList)
{
this.CategoryID = catID;
this.CategoryTitle = catTitle;
this.IssueTypeFilterList.Clear();
this.IssueTypeFilterList.AddRange(issueTypeList);
}
Don't forget read about 'Suppressing Notifications' here: https://docs.reactiveui.net/en/user-guide/lists/index.html
I think your problem is that you are not raising changes in your ReactiveList declaring it like:
public ReactiveList<IssueTypeSearchFilter> IssueTypeFilterList { get; set; }
Try this:
ReactiveList<IssueTypeSearchFilter> issueTypeFilterList
public ReactiveList<IssueTypeSearchFilter> IssueTypeFilterList
{
get { return issueTypeFilterList; };
set { this.RaiseAndSetIfChanged(ref issueTypeFilterList, value); };
}
Without the RaiseAndSetIfChanged you won`t be communicating changes to the view in your collection.
You can also subscribe to the observables that ReactiveList exposes like:
ItemsAdded
ItemsRemoved
ItemsMoved
Changed
etc
More info here: https://docs.reactiveui.net/en/user-guide/lists/index.html

Displaying child records in data grid in WPF using Entity Framework

I have two tables named Process and ProcessTriggers. ProcessTriggers referring Process table.And need to create a screen to diplay Process as well as the ProcessTrigger related to the Process. I have created data objects using entity framework and my classes look like.
public partial class Process
{
public Process()
{
this.ProcessTriggers = new ObservableCollection<ProcessTrigger>();
}
public int ID { get; set; }
public int WorkflowID { get; set; }
public string Name { get; set; }
public bool IsBatch { get; set; }
public Nullable<System.DateTime> ModifiedOn { get; set; }
public virtual ObservableCollection<ProcessTrigger> ProcessTriggers { get; set; }
}
And my view model look like..
public class ProcessViewModel : ViewModel<Process>
{
private RelayCommand saveCommand;
private RelayCommand cancelCommand;
public ProcessViewModel()
{
using(var context = new PACEContext())
{
this.Entity = context.Processes.FirstOrDefault(i => i.ID == 1);
IsInEditMode = true;
}
}
}
I am binding these viewModel to my View, Entity properties are bound correctly, but the ProcessTriggerCollection is not binding to datagrid..
<DataGrid ItemsSource="{Binding Entity.ProcessTriggers}" AutoGenerateColumns="True">
</DataGrid>
I juts started learning WPF, MVVM and Entity framework.Can any one help.?
I understand the Process have a ObservableCollection of other class called ProcessTriggers.
Why you don't create a ObservableCollection and binding this?
private ObservableCollection<ProcessTrigger> _listProcessTriggers;
public ObservableCollection<ProcessTrigger> ListProcessTriggers
{
get { return _listProcessTriggers; }
set { _listProcessTriggers= value; RaisePropertyChanged("ListProcessTriggers"); }
}
public ProcessViewModel()
{
using(var context = new PACEContext())
{
this.Entity = context.Processes.FirstOrDefault(i => i.ID == 1);
ListProcessTriggers = Entity.ProcessTriggers;
IsInEditMode = true;
}
}
And In Xmal Binding
<DataGrid ItemsSource="{Binding ListProcessTriggers}" AutoGenerateColumns="True />

How to fill in the DataGridTextColumn through part of the aggregated object?

I want to fill in the field DataGridTextColumn through part of the aggregated object.
I want to binding only a field Nazwa (not whole object Wynik) of the class Rezultat to the following DataGridTextColumn.
How I can do this ?
XAML:
<DataGridTextColumn Binding="{Binding Rezultat}" Header="Rezultat"/>
C#:
public class Rezultat
{
public int IdWyniku { get; set; }
public int IdZdarzenia { get; set; }
public int IdUżytkownika { get; set; }
public string Nazwa { get; set; }
public Rezultat() { }
public Rezultat(int IdZdarzenia, int IdUżytkownika, string Nazwa, int IdWyniku=0)
{
this.IdZdarzenia = IdZdarzenia;
this.IdUżytkownika = IdUżytkownika;
this.Nazwa = Nazwa;
this.IdWyniku = IdWyniku;
}
}
public class Zdarzenie
{
public Rezultat Wynik { get; set; }
}
C# (piece of code from another class):
this.ListaKuponów[0].ListaZdarzeń.Add(new Zdarzenie()
{
Wynik = new Rezultat(Convert.ToInt32(napis.ItemArray.GetValue(7)), this.IdRejestracji, wynik, idwyniku),
});
As pointed out earlier in the comments, you haven't shown us the property you are binding.
Assuming you implemented property Result of type Rezultat you'd need to bind to Nazwa in the markup as follows:
<DataGridTextColumn Binding="{Binding Result.Nazwa}" Header="Rezultat"/>
Note: If you haven't got implemented INotifyPropertyChanged on this class, you won't receive any updates if the field changes. You'd need to create either an extra ViewModel or implement INotifyPropertyChanged in your model. (Assuming that you are actually using the MVVM pattern)

MVVM NotifyPropertyChanged

I want to ask you, how can I reflect changes on View, when I changed source. Here's my code. MODEL:
public class ExecutionMode
{
private List<DayItem> _dayItems = new List<DayItem>();
public enum TypeMode
{
None,
Once,
Daily,
Weekly
}
public TypeMode Mode { get; set; }
public DateTime? ExecutionTime { get; set; }
public DateTime? ExecutionDate { get; set; }
public List<DayItem> DayItems { get { return _dayItems; } set { _dayItems = value; } }
}
public class ProfileRecord
{
private ExecutionMode _mode = new ExecutionMode();
public ExecutionMode ExecutionMode { get { return _mode; } set { _mode = value; } }
}
ViewModel
public class NewProfileViewModel : INotifyPropertyChanged
{private ProfileRecord _record = new ProfileRecord();
public ProfileRecord Record { get{return _record; } set{_record=value; OnPropertyChanged("Record")}}
XAML:
<toolkit:TimePicker Header="Time of execution:" Margin="12,0,70,0" Value="{Binding ProfileRecord.ExecutionMode.ExecutionTime, Mode=TwoWay}" Visibility="{Binding ElementName=lpickerExecutionModes, Path=SelectedItem, Converter={StaticResource VisibilityConvert}, ConverterParameter=Once|Daily|Weekly}" />
When I set somewhere in code Record.ExecutionTime = Time it doesn't reflect on View. So I'm asking. Should I implement NotifyPropertyChanged in Model too?
Thanks
There are 2 ways of solving the problem:
Implement INotifyPropertyChanged on your Model class
Map all necessary properties from Model object as a properties within ViewModel and implement INotifyPropertyChanged on those properties.
I prefer the first approach, especially when model is really huge, has a lot of properties, and all these properties are used in View.

Categories