I'm experiencing an issue with UI not updating when loading the properties from a Settings file. Beneath the code is shown, I apologize for the Dutch property names, translation is as follows:
Lade: Tray.
Papiergrootte: papersize.
Geselecteerde: selected.
isEersteKeer: isFirstTime.
I have a usercontrol, a printer combobox, to which the viewmodel code (below) is bound.
public class PrinterViewModel : ViewModelBase
{
private ObservableCollection<Printer> _printers;
private Printer _geselecteerdePrinter;
private ObservableCollection<Lade> _lades;
private ObservableCollection<Papiergrootte> _papiergroottes;
private Lade _geselecteerdeLade;
private Papiergrootte _geselecteerdePapiergrootte;
private bool isEersteKeer;
public PrinterViewModel()
{
isEersteKeer = true;
_printers = Printer.GetPrinters();
LoadPrinterSettings();
isEersteKeer = false;
}
public Lade GeselecteerdeLade
{
get { return _geselecteerdeLade; }
set
{
_geselecteerdeLade = value;
RaisePropertyChanged("GeselecteerdeLade");
if (!isEersteKeer)
{
SavePrinterSettings();
}
}
}
public Papiergrootte GeselecteerdePapiergrootte
{
get { return _geselecteerdePapiergrootte; }
set
{
_geselecteerdePapiergrootte = value;
RaisePropertyChanged("GeselecteerdePapiergrootte");
if (!isEersteKeer)
{
SavePrinterSettings();
}
}
}
public ObservableCollection<Lade> Lades
{
get { return _lades; }
set
{
_lades = value;
RaisePropertyChanged("Lades");
if (Lades.Count > 0 && !isEersteKeer)
{
GeselecteerdeLade = Lades[0];
}
}
}
public ObservableCollection<Papiergrootte> Papiergroottes
{
get { return _papiergroottes; }
set
{
_papiergroottes = value;
RaisePropertyChanged("Papiergroottes");
if (Papiergroottes.Count > 0 && !isEersteKeer)
{
GeselecteerdePapiergrootte = Papiergroottes[0];
}
}
}
public ObservableCollection<Printer> Printers
{
get { return _printers; }
set
{
_printers = value;
RaisePropertyChanged("Printers");
}
}
public Printer GeselecteerdePrinter
{
get { return _geselecteerdePrinter; }
set
{
_geselecteerdePrinter = value;
RaisePropertyChanged("GeselecteerdePrinter");
Lades = _geselecteerdePrinter.Lades;
Papiergroottes = _geselecteerdePrinter.PapierGroottes;
if (!isEersteKeer)
{
SavePrinterSettings();
}
}
}
private void SavePrinterSettings()
{
if (GeselecteerdePrinter != null && GeselecteerdeLade != null && GeselecteerdePapiergrootte != null)
{
Settings.Default.Printer1 = GeselecteerdePrinter;
Settings.Default.Lade1 = GeselecteerdeLade;
Settings.Default.Papiergrootte1 = GeselecteerdePapiergrootte;
Settings.Default.Save();
}
}
private void LoadPrinterSettings()
{
GeselecteerdePrinter = Settings.Default.Printer1;
GeselecteerdeLade = Settings.Default.Lade1;
GeselecteerdePapiergrootte = Settings.Default.Papiergrootte1;
}
}
And the XAML of the printer combobox:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:c="clr-namespace:ControlLib.uPrinterCombo"
xmlns:vm="clr-namespace:ControlLib.uPrinterCombo"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" x:Class="ControlLib.uPrinterCombo.uPrinterCombo"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
>
<UserControl.Resources>
<c:PrinterViewModel x:Key="PrinterViewModel"/>
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource PrinterViewModel}"/>
</UserControl.DataContext>
<Grid>
<ComboBox x:Name="cmbPrinters" HorizontalAlignment="Left" Margin="17,20,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Path=Printers, Mode=TwoWay}" DisplayMemberPath="Naam" SelectedItem="{Binding GeselecteerdePrinter}"/>
<ComboBox x:Name="cmbPapierGroottes" HorizontalAlignment="Left" Margin="17,50,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Path=Papiergroottes, Mode=TwoWay}" DisplayMemberPath="Naam" SelectedItem="{Binding GeselecteerdePapiergrootte}"/>
<ComboBox x:Name="cmbPapierLades" HorizontalAlignment="Left" Margin="17,80,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Path=Lades, Mode=TwoWay}" DisplayMemberPath="Naam" SelectedItem="{Binding GeselecteerdeLade}"/>
</Grid>
</UserControl>
Thanks in advance to anyone trying to help. I am sure the binding is working, it just doesn't work when I read the properties from the Settings.
The Properties change when loading the Settings, the selectedItem is just not updating eventhough the source of the combobox does update according to the Selected Printer loaded from Settings.
Edit: the code where the Collections are initialised:
namespace ControlLib.uPrinterCombo
{
public class Printer : ObservableObject
{
private string _naam;
private ObservableCollection<Lade> _lades;
private ObservableCollection<Papiergrootte> _papiergroottes;
public string Naam
{
get { return _naam; }
set { Set<string>(() => this.Naam, ref _naam, value); }
}
public ObservableCollection<Lade> Lades
{
get { return _lades; }
set { Set<ObservableCollection<Lade>>(() => this.Lades, ref _lades, value); }
}
public ObservableCollection<Papiergrootte> PapierGroottes
{
get { return _papiergroottes; }
set { Set<ObservableCollection<Papiergrootte>>(() => this.PapierGroottes, ref _papiergroottes, value); }
}
public static ObservableCollection<Printer> GetPrinters()
{
ObservableCollection<Printer> printers = new ObservableCollection<Printer>();
PrintServer localPrintServer = new PrintServer();
PrintQueueCollection printQueues = localPrintServer.GetPrintQueues(new[] { EnumeratedPrintQueueTypes.Local, EnumeratedPrintQueueTypes.Connections });
var printerQueues = (from printer in printQueues
select printer).ToList();
foreach (var printerQueue in printerQueues)
{
Printer p = new Printer();
XmlPrinterUtility util = new XmlPrinterUtility(printerQueue);
ObservableCollection<Lade> lades = new ObservableCollection<Lade>();
ObservableCollection<Papiergrootte> papiergroottes = new ObservableCollection<Papiergrootte>();
foreach (var lade in util.GetPrinterInfo(XmlPrinterUtility.PrinterInfo.papierLade))
{
lades.Add(new Lade(lade.Key, lade.Value));
}
foreach (var papiergrootte in util.GetPrinterInfo(XmlPrinterUtility.PrinterInfo.papierGrootte))
{
papiergroottes.Add(new Papiergrootte(papiergrootte.Key, papiergrootte.Value));
}
p.Lades = lades;
p.PapierGroottes = papiergroottes;
p.Naam = printerQueue.Name;
printers.Add(p);
}
return printers;
}
}
}
Related
I'm facing a problem where I can't reload a serialiazed object (using NewtonsoftJson) into cascading comboboxes. I'm also using the Prism MVVM lib.
While at first start my application is working as expected:
so I am able to select values from the second combobox based on the first combobox, but when I save the model and reload it I've two main issues:
The SelectedItem property never get set (even the debugger shows that it's not null)
The second combobox stays empty eventhough the values seems to be loaded, that looks like:
What am I doing wrong here? Also, I don't like the ComboboxSelectionChanged approach, so maybe someone can point me to the MVVM based approach.
Here is the minimal working example:
MainWindow.xaml.cs
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new ViewModel();
ConstructRandomData();
DataContext = viewModel;
}
private void ConstructRandomData()
{
// Construct data
for (int i = 0; i < 5; i++)
{
var ids = new List<SubId>();
for (int r = 0; r < 10; r++)
{
ids.Add(
new SubId
{
Name = $"Id_{i}.{r}"
}
);
}
viewModel.MainIds.Add(
new MainId
{
Name = $"MainId{i}",
SubIds = ids
});
}
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox combo = sender as ComboBox;
if (combo.SelectedItem is MainId selectedItem)
{
var subIdList = (from mainId in viewModel.MainIds
where mainId.Name.Equals(selectedItem.Name)
select mainId.SubIds).First();
viewModel.SubIds.Clear();
viewModel.SubIds.AddRange(subIdList.ToArray());
}
}
private void SaveButtton_Click(object sender, RoutedEventArgs e)
{
File.WriteAllText("savedData.json", JsonConvert.SerializeObject(viewModel));
}
private void LoadButton_Click(object sender, RoutedEventArgs e)
{
ViewModel deserializedModel = JsonConvert.DeserializeObject<ViewModel>(File.ReadAllText("savedData.json"));
viewModel.MainIds = deserializedModel.MainIds;
viewModel.SubIds = deserializedModel.SubIds;
}
}
public class ViewModel : BindableBase
{
public ObservableCollection<MainId> MainIds { get; set; } = new ObservableCollection<MainId>();
public ObservableCollection<SubId> SubIds { get; set; } = new ObservableCollection<SubId>();
private MainId mainId;
public MainId SelectedMainId
{
get { return mainId; }
set { SetProperty(ref mainId, value); }
}
private SubId selectedId;
public SubId SelectedId
{
get { return selectedId; }
set { SetProperty(ref selectedId, value); }
}
}
public class MainId : BindableBase
{
private string name;
public string Name
{
get { return name; }
set
{
SetProperty(ref name, value);
}
}
public List<SubId> SubIds { get; set; } = new List<SubId>();
}
public class SubId : BindableBase
{
private string name;
public string Name
{
get { return name; }
set { SetProperty(ref name, value); }
}
}
The MainWindow.xaml
<Window x:Class="CascadingComboBox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CascadingComboBox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Margin="30">
<ComboBox Margin="5" Width="150"
ItemsSource="{Binding MainIds}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedMainId}"
SelectionChanged="ComboBox_SelectionChanged"/>
<ComboBox Margin="5" Width="150"
ItemsSource="{Binding SubIds}"
SelectedItem="{Binding SelectedId}"
DisplayMemberPath="Name"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="10">
<Button Margin="5" Width="50" Content="Save" Click="SaveButtton_Click" />
<Button Margin="5" Width="50" Content="Load" Click="LoadButton_Click"/>
</StackPanel>
</StackPanel>
</Window>
During serialization, SelectedItem contains an object from ComboBoxItems collection.
But after deserialization this is no longer true: now SelectedItem is a new instance, even though it has same content as one of the items in ComboBoxItems. This is how Json.NET works by default.
You can fix that by changing PreserveReferencesHandling option like this:
var jsonSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects };
JsonConvert.SerializeObject(model, Formatting.Indented, jsonSettings);
...
model = JsonConvert.DeserializeObject<List<Person>>(json, jsonSettings);
https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_PreserveReferencesHandling.htm
In our edit page, we have an issue to populate the value into Selected Item in picker and it won't select for some reason from the LoadCourses() or LoadRoundCategories() either.
Any ideas?
Here's the code:
ViewModel
public class EditGolfRoundViewModel : INotifyPropertyChanged
{
ApiServices _apiServices = new ApiServices();
private string _message;
private ObservableCollection<GolfCourse> _courses;
private ObservableCollection<GolfRoundCategory> _roundCategories;
private object_selectedGolfCourse;
private GolfRoundCategory _selectedGolfRoundCategory;
private GolfRound _golfRound;
public EditGolfRoundViewModel()
{
_selectedGolfCourse = new GolfCourse();
_selectedGolfRoundCategory = new GolfRoundCategory();
LoadCourses();
LoadRoundCategories();
}
public GolfRound GolfRound
{
get { return _golfRound; }
set
{
_golfRound = value;
OnPropertyChanged();
}
}
public string Message
{
get { return _message; }
set
{
_message = value;
OnPropertyChanged();
}
}
public ObservableCollection<GolfCourse> GolfCourses
{
get { return _courses; }
set
{
if (_courses != value)
{
_courses = value;
OnPropertyChanged();
}
}
}
public ObservableCollection<GolfRoundCategory> GolfRoundCategories
{
get { return _roundCategories; }
set
{
_roundCategories = value;
OnPropertyChanged();
}
}
public object SelectedGolfCourse
{
get { return _selectedGolfCourse; }
set
{
_selectedGolfCourse = value;
var golfCourse = _selectedGolfCourse as GolfCourse;
Guid tempGolfCourseID = golfCourse.GolfCourseID;
OnPropertyChanged("SelectedGolfCourse");
}
}
public GolfRoundCategory SelectedGolfRoundCategory
{
get { return _selectedGolfRoundCategory; }
set
{
_selectedGolfRoundCategory = value;
OnPropertyChanged();
}
}
public ICommand EditCommand
{
get
{
return new Command(async() =>
{
GolfRound.GolfCourseID = SelectedGolfCourse.GolfCourseID;
GolfRound.GolfCourse = SelectedGolfCourse;
GolfRound.GolfRoundCategoryID = SelectedGolfRoundCategory.GolfRoundCategoryID;
GolfRound.GolfRoundCategory = SelectedGolfRoundCategory;
GolfRound.LastModifiedUTC = System.DateTime.Now;
await _apiServices.PutGolfRoundAsync(GolfRound, Settings.AccessToken);
});
}
}
public ICommand DeleteCommand
{
get
{
return new Command(async () =>
{
await _apiServices.DeleteGolfRoundAsync(GolfRound.GolfRoundID, Settings.AccessToken);
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private async void LoadCourses()
{
GolfCourses = new ObservableCollection<GolfCourse>(await _apiServices.GetGolfCoursesAsync(Settings.AccessToken));
}
private async void LoadRoundCategories()
{
GolfRoundCategories = new ObservableCollection<GolfRoundCategory>(await _apiServices.GetGolfRoundCategoriesAsync(Settings.AccessToken));
}
}
View - XAML
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:AthlosifyMobile.ViewModels.Golf"
x:Class="AthlosifyMobile.Views.EditGolfRoundPage">
<StackLayout Orientation="Vertical" VerticalOptions="Center" Spacing="30" Padding="30">
<Entry Text="{Binding GolfRound.Name}" Placeholder="name" FontSize="Default" />
<Entry Text="{Binding GolfRound.Notes}" Placeholder="notes" FontSize="Default" />
<Entry Text="{Binding GolfRound.DailyHandicap}" Placeholder="daily handicap" FontSize="Default" />
<Label Text="Date" />
<DatePicker Date="{Binding GolfRound.TeeOffUTC}"
Format="D"
Margin="30, 0, 0, 30" />
<Picker x:Name="pCourse" Title="Course" ItemsSource="{Binding GolfCourses}"
SelectedItem="{Binding SelectedGolfCourse, Mode=TwoWay}"
ItemDisplayBinding="{Binding Name}"></Picker>
<Entry Text="{Binding GolfRound.GolfCourse.Name}" Placeholder="selected golf course" FontSize="Default" />
<Picker x:Name="pCategory" Title="Category" ItemsSource="{Binding GolfRoundCategories}"
SelectedItem="{Binding SelectedGolfRoundCategory, Mode=TwoWay}"
ItemDisplayBinding="{Binding Name}"></Picker>
<Entry Text="{Binding SelectedGolfRoundCategory.Name}" Placeholder="selected round category" FontSize="Default" />
<Button Command="{Binding EditCommand}" Text="Edit Round" />
<Button Command="{Binding DeleteCommand}" Text="Delete Round" />
<Label Text="{Binding Message}" ></Label>
</StackLayout>
View - code behind
public partial class EditGolfRoundPage : ContentPage
{
public EditGolfRoundPage (GolfRound round)
{
var editGolfRoundViewModel = new EditGolfRoundViewModel();
editGolfRoundViewModel.GolfRound = round;
BindingContext = editGolfRoundViewModel;
InitializeComponent ();
//var editGolfRoundViewModel = new EditGolfRoundViewModel();
//editGolfRoundViewModel.GolfRound = round;
//editGolfRoundViewModel.SelectedGolfCourse = round.GolfCourse;
//BindingContext = editGolfRoundViewModel;
}
}
Implement IEquatable for class of property used in SelectedItem:
public class GolfCourse : IEquatable<GolfCourse>
{
...
public bool Equals(GolfCourse other)
{
if (other == null) return false;
return (this.Name.Equals(other.Name));
}
}
Usage, assuming ItemsSource contains an object with value of Name as shown below:
SelectedGolfCourse = new GolfCourse { Name = "Course 2" };
The following is true as of Xamarin Forms v4.7.
See if you are doing this:
<Picker x:Name="DefaultEntitlement" Title="Entitlement"
SelectedItem="{Binding SelectedOwnerEntitlement, Mode=TwoWay}"
ItemsSource="{Binding OwnerEntitlements, Mode=TwoWay}">
</Picker>
Instead of this:
<Picker x:Name="DefaultEntitlement" Title="Entitlement"
ItemsSource="{Binding OwnerEntitlements, Mode=TwoWay}"
SelectedItem="{Binding SelectedOwnerEntitlement, Mode=TwoWay}">
</Picker>
you are binding view model before Initialise page so that is wrong thing we can not bind data without Initialise page fro that you need to change code of xaml.cs like below
public EditGolfRoundPage (GolfRound round)
{
InitializeComponent ();
BindingContext = editGolfRoundViewModel;
BindingContext.GolfRound = round;
}
that will work for you
Happy Coding :)
Xaml
<Picker x:Name="ProductPicker" WidthRequest="220" HeightRequest="35" Title="Select" ItemsSource="{Binding ProductList}" SelectedItem="{Binding ProductSelected}" ItemDisplayBinding="{Binding ProductName}"> </Picker>
ViewModel
public List<ProductModel> ProductList { get; set; }
Populating Data in Datasource in Viewmodel
ProductList = Products.Result.ToList();
Getting Selected Data
private object _ProductSelected;
public object ProductSelected
{
get { return _ProductSelected; }
set
{
_ProductSelected = value;
ProductSelected_SelectedIndex.Execute(value);
OnPropertyChanged("ProductSelected"); //in case you are using MVVM Light
}
}
private Command ProductSelected_SelectedIndex
{
get
{
return new Command((e) =>
{
}}}
private object _CitySelectedFromList;
public object CitySelectedFromList
{
get { return _CitySelectedFromList; }
set
{
_CitySelectedFromList = value;
var cityid = _CitySelectedFromList as CityMasterModel;
tempcityids = Convert.ToInt32(cityid.Id);
}
}
Can you try once replacing your Viewmodel. I have changed the type from Object to actual type. Set the Default item while loading the items from endpoint.
public class EditGolfRoundViewModel : INotifyPropertyChanged
{
ApiServices _apiServices = new ApiServices();
private string _message;
private ObservableCollection<GolfCourse> _courses;
private ObservableCollection<GolfRoundCategory> _roundCategories;
private GolfCourse _selectedGolfCourse;
private GolfRoundCategory _selectedGolfRoundCategory;
private GolfRound _golfRound;
public EditGolfRoundViewModel()
{
LoadCourses();
LoadRoundCategories();
}
public GolfRound GolfRound
{
get { return _golfRound; }
set
{
_golfRound = value;
OnPropertyChanged();
}
}
public string Message
{
get { return _message; }
set
{
_message = value;
OnPropertyChanged();
}
}
public ObservableCollection<GolfCourse> GolfCourses
{
get { return _courses; }
set
{
if (_courses != value)
{
_courses = value;
OnPropertyChanged();
}
}
}
public ObservableCollection<GolfRoundCategory> GolfRoundCategories
{
get { return _roundCategories; }
set
{
_roundCategories = value;
OnPropertyChanged();
}
}
public GolfCourse SelectedGolfCourse
{
get { return _selectedGolfCourse; }
set
{
_selectedGolfCourse = value;
OnPropertyChanged("SelectedGolfCourse");
}
}
public GolfRoundCategory SelectedGolfRoundCategory
{
get { return _selectedGolfRoundCategory; }
set
{
_selectedGolfRoundCategory = value;
OnPropertyChanged();
}
}
public ICommand EditCommand
{
get
{
return new Command(async () =>
{
GolfRound.GolfCourseID = SelectedGolfCourse.GolfCourseID;
GolfRound.GolfCourse = SelectedGolfCourse;
GolfRound.GolfRoundCategoryID = SelectedGolfRoundCategory.GolfRoundCategoryID;
GolfRound.GolfRoundCategory = SelectedGolfRoundCategory;
GolfRound.LastModifiedUTC = System.DateTime.Now;
await _apiServices.PutGolfRoundAsync(GolfRound, Settings.AccessToken);
});
}
}
public ICommand DeleteCommand
{
get
{
return new Command(async () =>
{
await _apiServices.DeleteGolfRoundAsync(GolfRound.GolfRoundID, Settings.AccessToken);
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private async void LoadCourses()
{
GolfCourses = new ObservableCollection<GolfCourse>(await _apiServices.GetGolfCoursesAsync(Settings.AccessToken));
if (GolfCourses != null && GolfCourses.Count() > 0)
SelectedGolfCourse = GolfCourses[0];
}
private async void LoadRoundCategories()
{
GolfRoundCategories = new ObservableCollection<GolfRoundCategory>(await _apiServices.GetGolfRoundCategoriesAsync(Settings.AccessToken));
if (GolfRoundCategories != null && GolfRoundCategories.Count() > 0)
SelectedGolfRoundCategory = GolfRoundCategories[0];
}
}
You can try:
make sure your prop support INotifyPropertyChanged
make sure Selected Item variable has initial value
try debug your selected item variable, make sure has value
1. Initials
Take a look your code:
public EditGolfRoundViewModel()
{
_selectedGolfCourse = new GolfCourse();
_selectedGolfRoundCategory = new GolfRoundCategory();
LoadCourses();
LoadRoundCategories();
}
If you try to initial value Selected Item, don't do this:
_selectedGolfCourse = new GolfCourse();
_selectedGolfRoundCategory = new GolfRoundCategory();
let it null, is fine. You can do like this:
SelectedGolfRoundCategory = new GolfRoundCategory();
//or
SelectedGolfRoundCategory = dataFromAPI;
2. Assign
Take a look your code:
public ICommand EditCommand
{
get
{
return new Command(async() =>
{
GolfRound.GolfCourseID = SelectedGolfCourse.GolfCourseID;
GolfRound.GolfCourse = SelectedGolfCourse;
GolfRound.GolfRoundCategoryID = SelectedGolfRoundCategory.GolfRoundCategoryID;
GolfRound.GolfRoundCategory = SelectedGolfRoundCategory;
GolfRound.LastModifiedUTC = System.DateTime.Now;
await _apiServices.PutGolfRoundAsync(GolfRound, Settings.AccessToken);
});
}
}
You trying selected item variable insert into to object GolfRound, like this part:
GolfRound.GolfRoundCategoryID = SelectedGolfRoundCategory.GolfRoundCategoryID;
GolfRound.GolfRoundCategory = SelectedGolfRoundCategory;
Make sure you have INotifyPropertyChanged implement this model GolfRound for prop GolfRoundCategoryID and GolfRoundCategory. If not, it would not work. I have experience for this.
Hope this helpful.
How can I search for a row by a column and later select that row in a XamDataGrid.
I've tried iterating over the DataSource, but the type of the elements isn't very helpful, it has only a HasData bool property exposed.
Try to use XamDataGrid.FieldLayouts.DataPresenter.Records collection and check for the required cell. When the record is found it can be selected by setting record.IsSelected = true;
Something like that:
using System;
using System.Windows;
using System.Windows.Media;
using System.Collections.ObjectModel;
using System.ComponentModel;
using Infragistics.Windows.DataPresenter;
namespace IGFindRow
{
public partial class MainWindow : Window
{
public MainWindow()
{
_cars = Cars;
InitializeComponent();
}
#region Code fragment from samples provided by Infragistics
public ObservableCollection<Car> _cars = null;
public ObservableCollection<Car> Cars
{
get
{
if (this._cars == null)
{
this._cars = new ObservableCollection<Car>();
this._cars.Add(new Car("Dodge", "Ram", Colors.Blue, 22050.00, 153));
this._cars.Add(new Car("Ford", "Explorer", Colors.Green, 27175.00, 96));
this._cars.Add(new Car("BMW", "Z4", Colors.Silver, 35600.00, 42));
this._cars.Add(new Car("Toyota", "Camry", Colors.Black, 20790.99, 131));
}
return _cars;
}
}
public class Car : INotifyPropertyChanged
{
string m_make;
string m_model;
Color m_color;
double m_baseprice;
int m_milage;
public Car(string make, string model, Color color, double baseprice, int milage)
{
this.Make = make;
this.Model = model;
this.Color = color;
this.BasePrice = baseprice;
this.Milage = milage;
}
public string Make
{
get { return m_make; }
set
{
if (m_make != value)
{
m_make = value;
NotifyPropertyChanged("Make");
}
}
}
public string Model
{
get { return m_model; }
set
{
if (m_model != value)
{
m_model = value;
NotifyPropertyChanged("Model");
}
}
}
public Color Color
{
get { return m_color; }
set
{
if (m_color != value)
{
m_color = value;
NotifyPropertyChanged("Color");
}
}
}
public double BasePrice
{
get { return m_baseprice; }
set
{
if (m_baseprice != value)
{
m_baseprice = value;
NotifyPropertyChanged("BasePrice");
}
}
}
public int Milage
{
get { return m_milage; }
set
{
if (m_milage != value)
{
m_milage = value;
NotifyPropertyChanged("Milage");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info));
}
}
#endregion
private void Search_Click(object sender, RoutedEventArgs e)
{
// Enumerate records
foreach (var it in dataGrid.FieldLayouts.DataPresenter.Records)
{
if (it is DataRecord record)
{
// Check the current column value
if (record.Cells["Make"].Value.ToString().ToUpper() == Maker.Text.ToUpper())
{
record.IsSelected = true;
break;
}
}
}
}
}
}
The XAML:
<Window x:Class="IGFindRow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ig="http://infragistics.com/DataPresenter"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
Name="dgTest">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="5,5,5,5">
<Label Name="ColumnName" Padding="5,5,0,5">Maker:</Label>
<TextBox Name="Maker" Padding="5,5,5,5" Margin="3,0,20,0">Ford</TextBox>
<Button Name="Search" Padding="5,5,5,5" Click="Search_Click">Press to search</Button>
</StackPanel>
<ig:XamDataGrid x:Name="dataGrid" Grid.Row="1"
IsGroupByAreaExpanded="False"
GroupByAreaLocation="None"
Theme="Generic"
DataSource="{Binding ElementName=dgTest, Path=Cars}">
<ig:XamDataGrid.FieldLayoutSettings>
<ig:FieldLayoutSettings SelectionTypeRecord="Single" />
</ig:XamDataGrid.FieldLayoutSettings>
<ig:XamDataGrid.ViewSettings>
<ig:GridViewSettings/>
</ig:XamDataGrid.ViewSettings>
<ig:XamDataGrid.FieldSettings>
<ig:FieldSettings CellClickAction="SelectRecord" AllowRecordFiltering="True"/>
</ig:XamDataGrid.FieldSettings>
</ig:XamDataGrid>
</Grid>
</Window>
I'm reworking an old control to WPF. I need to make a datagrid which displays a list of alarms. The first column of the datagrid is a datagridtemplate column which holds a flag icon and the date. When the user presses the button the alarms are acknowledged and the flag icon has to disappear. I'm using an MVVM pattern.
The problem is that I cannot seem to update the flag in the datagridtemplate column. When I change the acknowledge status from 0 to 1 (the alarm was ackonwledged), the flag does not disappear. When I reload the control and get the alarms again from the server, the flag does not show up again indicating that its status has actually been updated in the server. So somehow my code is working but my UI is not getting updated. I also tried to change the alarm's description in the same way and that column does get updated. (MainViewModelBase implements INotifyPropertyChanged)
The view
<DataGrid x:Class="Kwa.Presentation.Views.AlarmList.AlarmList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Kwa.Presentation.Views.AlarmList"
xmlns:components="clr-namespace:Kwa.Presentation.Components"
xmlns:converters="clr-namespace:Kwa.Presentation.Converters"
xmlns:Trans="clr-namespace:Kwa.Presentation.Resources"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="750"
ItemsSource="{Binding Alarms}"
SelectedItem="{Binding SelectedAlarm}"
IsSynchronizedWithCurrentItem="True"
CanUserResizeColumns="True" IsReadOnly="True" CanUserReorderColumns="False" CanUserSortColumns="False" SelectionMode="Single" CanUserAddRows="False"
Background="White" RowHeaderWidth="0" AutoGenerateColumns="False" GridLinesVisibility="None" RowHeight="{Binding Rowheight}" FrozenColumnCount = "1"
ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto"
x:Name="AlarmFramework"
SizeChanged="AlarmFramework_SizeChanged"
>
<DataGrid.Resources>
<converters:AlarmSeverityToColorConverter x:Key="AlarmSeverityToColorConverter"/>
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<converters:InvertedBoolToVisibilityConverter x:Key="InvertedBoolToVisibilityConverter"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="{x:Static Trans:TranslatedResources.AlarmColumnHeaderTime}" Width="auto" HeaderStyle="{StaticResource WithButt}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<components:FlagControl VerticalAlignment="Center" Height="15" Width="15" FlagColor="{Binding Severity, Converter={StaticResource AlarmSeverityToColorConverter}}"
Visibility="{Binding AckStatus, Converter={StaticResource InvertedBoolToVisibilityConverter}, Mode=TwoWay}"/>
<TextBlock Text="{Binding DateTimeString}" Padding="10,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="{x:Static Trans:TranslatedResources.AlarmColumnHeaderSeverity}" Binding="{Binding SeverityString}" Width="auto" HeaderStyle="{StaticResource WithoutButt}"/>
<DataGridTextColumn Header="{x:Static Trans:TranslatedResources.AlarmColumnHeaderDescription}" Binding="{Binding Description}" d:Width="400" Width="*" HeaderStyle="{StaticResource WithoutButt}"/>
</DataGrid.Columns>
</DataGrid>
The AlarmList View Model
public class AlarmListViewModel : MainViewModelBase
{
#region Properties
private ObservableCollection<AlarmEntryViewModel> _alarms = new ObservableCollection<AlarmEntryViewModel>();
public ObservableCollection<AlarmEntryViewModel> Alarms
{
get { return _alarms; }
set
{
_alarms = value;
RaisePropertyChanged(() => Alarms);
}
}
private AlarmEntryViewModel _selectedAlarm;
public AlarmEntryViewModel SelectedAlarm
{
get { return _selectedAlarm; }
set
{
_selectedAlarm = value;
RaisePropertyChanged(() => SelectedAlarm);
}
}
private int _acknowledgeAllowed;
public int AcknowledgeAllowed
{
get { return _acknowledgeAllowed; }
set
{
_acknowledgeAllowed = value;
RaisePropertyChanged(() => AcknowledgeAllowed);
}
}
private readonly IActionCommand _acknowledgeCommand;
public IActionCommand AcknowledgeCommand
{
get { return _acknowledgeCommand; }
}
public int MaxAcknowledgedAlarm;
public double RowHeight { get; set; }
public AlarmEntryViewModel AlarmToSend { get; set; }
#endregion
#region Constructor
public AlarmListViewModel()
{
if (!IsInDesignMode)
{
RowHeight = 30;
}
//Add command
_acknowledgeCommand = new ActionCommand<double>(p => Acknowledge(p));
}
#endregion
#region Private Methods
private void Acknowledge(double parameter)
{
if (AcknowledgeAllowed == 1)
{
try
{
//Get Datagrid width
double DatagridWidth = (double)parameter;
//Calculate the amount of fully visible alarms
int AmountVisible = (int)Math.Floor(DatagridWidth / RowHeight);
if (Alarms.Count < AmountVisible)
{
AlarmToSend = Alarms[Alarms.Count - 1];
foreach(AlarmEntryViewModel alarm in Alarms)
{
alarm.AckStatus = true;
alarm.Description = "Iets nieuw";
}
}
else
{
//Send the last visible alarm, 0 based => -1
AlarmToSend = Alarms[AmountVisible - 1];
//Go to next visible alarm
SelectedAlarm = Alarms[AmountVisible];
}
//Acknowledge alarms
_proxy.Send(AlarmToSend.AlarmNumber);
}
catch (Exception ex)
{
_viewManager.ShowDialog(new MessageDialogViewModel()
{
AskAnswer = false,
Text = ex.Message,
Title = TranslatedResources.AlarmAckSendErrorTitle,
});
}
}
else
{
_viewManager.ShowDialog(new MessageDialogViewModel()
{
AskAnswer = false,
Text = TranslatedResources.AlarmAcknErrorMessage,
Title = TranslatedResources.AlarmAcknErrorTitle,
});
}
}
#endregion
The AlarmListEntry view model
public class AlarmEntryViewModel : MainViewModelBase
{
#region Fields
private readonly IViewManager _viewManager;
private readonly IDockManager _dockManager;
private string _description;
#endregion
#region Constructor
public AlarmEntryViewModel()
{
}
#endregion
#region Model & Proxy
private Alarm _model;
public Alarm Model
{
set { _model = value; }
}
public AlarmListServiceProxy Proxy { get; set; }
#endregion
#region Properties
public DateTime Time { get { return _model.Time; } }
public string DateTimeString { get { return _model.Time.ToString("dd/MM/yyyy hh:mm:ss"); } }
public int Severity { get { return _model.Severity; } }
public string SeverityString
{
get
{
if (_model.Severity == 0)
return "Melding";
if (_model.Severity == 1)
return "Waarschuwing";
if (_model.Severity == 2)
return "Fout";
else
return "Niet gekend";
}
}
public string Description
{
get
{
//string substring = _model.Description.Substring(_model.Description.Length - 1);
//if ( substring.Equals("\n"))
//{
// return _model.Description.Substring(0, _model.Description.Length - 1);
//}
//else
// return _model.Description;
return _description;
}
set
{
_description = value;
RaisePropertyChanged(() => Description);
}
}
public int AlarmNumber { get { return _model.Number; } }
public int AcknStatus { get { return _model.AcknStatus; } }
private bool _ackStatus;
public bool AckStatus
{
get
{
if (_model.AcknStatus == 0)
return false;
else
return true;
}
set
{
_ackStatus = value;
RaisePropertyChanged(() => AckStatus);
}
}
#endregion
}
You set _ackStatus in the setter but doesn't return the value of this field from the getter in AlarmEntryViewModel. Do this:
private bool _ackStatus;
public bool AckStatus
{
get
{
return _ackStatus;
}
set
{
_ackStatus = value;
RaisePropertyChanged(() => AckStatus);
}
}
Or set the _model.AcknStatus field in the setter:
private bool _ackStatus;
public bool AckStatus
{
get
{
if (_model.AcknStatus == 0)
return false;
else
return true;
}
set
{
_ackStatus = value;
_model.AcknStatus = value ? 1 : 0;
RaisePropertyChanged(() => AckStatus);
}
}
In my WPF Window I have a DataGrid control, with its ItemsSource bound to an ObservableCollection of items (let's say a simple object with a couple properties):
XAML: (Removed some xmlns stuff for brevity)
<Window>
<Window.Resources>
<CollectionViewSource x:Key="MyViewSource"
Source="{Binding MyItemList}"
Filter="MyItemList_Filter"/>
</Window.Resources>
<Window.DataContext>
<!-- Some Ioc stuff -->
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding TextFilter}" />
<DataGrid Grid.Row="1" x:Name="dataGrid"
ItemsSource="{Binding Source={StaticResource MyViewSource}}"
SelectionUnit="FullRow"
SelectionMode="Extended"
CanUserAddRows="False"
CanUserDeleteRows="False"
HeadersVisibility="Column" />
</StackPanel>
</Window>
ViewModel (cs):
public class ViewModel : ViewModelBase // From Galasoft MVVM Light toolkit
{
#region TextFilter Property
public const string TextFilterPropertyName = "TextFilter";
private string _TextFilter;
public string TextFilter
{
get
{
return _TextFilter;
}
set
{
if (_TextFilter == value)
{
return;
}
_TextFilter = value;
RaisePropertyChanged(TextFilterPropertyName);
}
}
#endregion // TextFilter Property
#region MyItemList Property
public const string MyItemListPropertyName = "MyItemList";
private ObservableCollection<Item> _MyItemList;
public ObservableCollection<Item> MyItemList
{
get
{
return _MyItemList;
}
set
{
if (_MyItemList == value)
{
return;
}
_MyItemList = value;
RaisePropertyChanged(MyItemListPropertyName);
}
}
#endregion // MyItemList Property
}
Filter method, from Window's code behind:
private void MyItemList_Filter(object sender, FilterEventArgs e)
{
var vm = (ViewModel)this.DataContext;
var item = (Item)e.Item;
// ...Simplified...
e.Accepted = item.PropertyToCheck.Contains(vm.TextFilter);
}
Filtering is applied only when filling MyItemList: how can I make the MyItemList_Filter be called (and DataGrid items be shown/hidden accordingly) on "live" TextFilter change?
Any help would be appreciated
You could (should) move the filtering logic to the view model, e.g.:
public class ViewModel : ViewModelBase
{
public const string TextFilterPropertyName = "TextFilter";
private string _TextFilter;
public string TextFilter
{
get
{
return _TextFilter;
}
set
{
if (_TextFilter == value)
return;
_TextFilter = value;
RaisePropertyChanged(TextFilterPropertyName);
Filter();
}
}
public const string MyItemListPropertyName = "MyItemList";
private ObservableCollection<Item> _MyItemList;
public ObservableCollection<Item> MyItemList
{
get
{
return _MyItemList;
}
set
{
if (_MyItemList == value)
return;
_MyItemList = value;
RaisePropertyChanged(MyItemListPropertyName);
}
}
private ObservableCollection<Item> _filtered;
public ObservableCollection<Item> FilteredList
{
get
{
return _filtered;
}
set
{
if (_filtered == value)
return;
_filtered = value;
RaisePropertyChanged("FilteredList");
}
}
private void Filter()
{
_filtered.Clear();
foreach(var item in _MyItemList)
{
if (item.PropertyToCheck.Contains(TextFilter))
_filtered.Add(item);
}
}
}
That's where it belongs. Then you don't need to the CollectionViewSource:
<DataGrid Grid.Row="1" x:Name="dataGrid" ItemsSource="{Binding FilteredList}" ... />
This can now be achieved using the NuGet package DataGridExtensions.