Data not displaying in columns in grid UWP - sqlite

I have a database linked to my grid. When adding data a new checkbox appears, so it is registering that something has been entered, but, the columns are all blank.
Im pretty sure the issue is that I have the bindings set up for the class Person. Could I either still use this class with some additional code, or, find a way to separate the data from the database into columns?
Relevant Code:
Database Class method:
public static List<string> Grab_Entries()
{
List<string> entries = new List<string>();
using (SqliteConnection db = new SqliteConnection("Filename=sqliteSample.db"))
{
db.Open();
SqliteCommand selectCommand = new SqliteCommand("SELECT * from EmployeeTable", db);
SqliteDataReader query;
try
{
query = selectCommand.ExecuteReader();
}
catch (SqliteException)
{
//Handle error
return entries;
}
while (query.Read())
{
entries.Add(query.GetString(0));
}
db.Close();
}
return entries;
MainPage method:
public void EmployeeGrid_Loaded(object sender, RoutedEventArgs e)
{
EmployeeGrid.ItemsSource = DB.Grab_Entries();
}
Main Page XAML:
<controls:DataGrid x:Name="EmployeeGrid" Margin="170,55,35,35"
ItemsSource="{x:Bind persons}"
CanUserSortColumns="True"
AutoGenerateColumns="False" Background="Black" Loaded="EmployeeGrid_Loaded">
<controls:DataGrid.Columns>
<controls:DataGridTextColumn Header="Employee ID"
Binding="{Binding PersonId}"/>
<controls:DataGridTextColumn Header="First Name"
Binding="{Binding FirstName}"/>
<controls:DataGridTextColumn Header="Last Name"
Binding="{Binding LastName}"/>
<controls:DataGridTextColumn Header="Address"
Binding="{Binding Address}"/>
<controls:DataGridTextColumn Header="Position"
Binding="{Binding Position}"/>
<controls:DataGridTextColumn Header="Pay Rate (ph)"
Binding="{Binding PayratePH}"/>
<controls:DataGridTextColumn Header="Sex"
Binding="{Binding Sex}"/>
<controls:DataGridTextColumn Header="TaxCode"
Binding="{Binding TaxCode}"/>
<controls:DataGridTextColumn Header="Email"
Binding="{Binding Email}"/>
<controls:DataGridTextColumn Header="Emergency Contact"
Binding="{Binding EmergencyDetails}"/>
<controls:DataGridCheckBoxColumn Header="Selected"
/>
</controls:DataGrid.Columns>
</controls:DataGrid>
Ill add my Person class too:
public class Person
{
public int PersonId { get; set; }
public int DepartmentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Position { get; set; }
public string Address { get; set; }
public double PayratePH { get; set; }
public string Sex { get; set; }
public string TaxCode { get; set; }
public string EmergencyDetails { get; set; }
public string Email { get; set; }

I can spot two things here. First, the main page XAML binds the items source to persons, which gets overwritten with the return value of DB.Grab_Entries() whenever the control loads.
In EmployeeGrid_Loaded(), you should be populating the collection that the ItemsSource is bound to (persons), instead of assigning a new collection.
The loaded event should look something like this:
public void EmployeeGrid_Loaded(object sender, RoutedEventArgs e)
{
this.persons.Clear();
this.persons.AddRange(DB.Grab_Entries());
}
P.S. make sure that the ItemsSource collection is of type ObservableCollection, because otherwise it will not update the binding when the elements change.
Second, the method DB.Grab_Entries() returns a list of strings, but by the looks of it, the data grid and the persons collection is expecting a list of person objects with certain properties.
When you're reading the data from the sqlite query, you're getting a single column of data at a time (query.GetString(0)). You'll need to construct an object with all column values and put the object into a list.

As #Darius S. mentioned, the ItemsSource of DataGrid you bound with is the lists of Person class, however, the DB.Grab_Entries() method returns the lists of string type, so the DataGrid can't display well. In your Grab_Entries method, you could get the value each property and then convert them into the Person class. After that, add the Person class into entries lists.
In addition, it is recommended to use ObservableCollection class, when you insert or remove data from this class, it will automatically update the UI. So it's better to return ObservableCollection type from your Grab_Entries method directly. I take the FirstName property as an example:
public async static Task<ObservableCollection<Person>> Grab_Entries()
{
ObservableCollection<Person> entries = new ObservableCollection<Person>();
string dbpath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "sqliteSample.db");
using (SqliteConnection db = new SqliteConnection($"Filename={dbpath}"))
{
db.Open();
SqliteCommand selectCommand = new SqliteCommand("SELECT * from EmployeeTable", db);
using (var reader = await selectCommand.ExecuteReaderAsync())
{
var nameOrdinal = reader.GetOrdinal("First_Name");
//The same method to get other properties
while (await reader.ReadAsync())
{
entries.Add(new Person() { FirstName = reader.GetString(nameOrdinal) });
}
}
db.Close();
}
return entries;
}
MainPage.cs:
ObservableCollection<Person> persons;
public async void EmployeeGrid_Loaded(object sender, RoutedEventArgs e)
{
persons = await Person.Grab_Entries();
EmployeeGrid.ItemsSource = persons;
}

Related

Avalonia Datagrid cell value does not update when model property is updated from another column

I have a Datagrid with two columns that bind to the same property
<DataGrid
Margin="10"
BorderBrush="Black"
BorderThickness="1"
Grid.Row="1"
Grid.ColumnSpan="3"
Items="{Binding Logs}"
AutoGenerateColumns="False"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="Temp Date"
Binding="{Binding Date,Mode=TwoWay}"
>
</DataGridTextColumn>
<DataGridTemplateColumn
Header="Calendar Column"
CellTemplate="{Binding TestTemplate}"
CellEditingTemplate="{Binding EditingTemplate}"
>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I have also created a cell template in the view model as such:
// Cell Data Template
TestTemplate = new FuncDataTemplate<EntryLog>((value, namescope) =>
new TextBlock
{
[!TextBlock.TextProperty] = new Binding("Date",BindingMode.TwoWay),
});
// Cell Editing Data Template
EditingTemplate = new FuncDataTemplate<EntryLog>((value, namescope) =>
{
var grid = new Grid();
var tb = new TextBlock
{
[!TextBlock.TextProperty] = new Binding("Date", BindingMode.TwoWay),
};
grid.Children.Add(tb);
var calendar = new Calendar();
calendar.DisplayDate = value.Date;
calendar.SelectedDate = value.Date;
Popup popup = new Popup();
popup.Child = calendar;
popup.IsOpen = true;
calendar.SelectedDatesChanged += (s, e) =>
{
value.Date = calendar.SelectedDate.Value.Date;
//tb.Text = value.Date.ToString();
};
grid.Children.Add(popup);
return grid;
});
In the UI, it looks like this when editing:
My issue is, whenever I update one column, the other column does not get updated. The itemsource is a Observable Collection of my model
My model:
public class EntryLog : INotifyPropertyChanged
{
private DateTime _date;
public DateTime Date
{
get => _date;
set
{
if (_date != value)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Date)));
_date = value;
}
}
public string Description { get; set; }
public double Hours { get; set; }
public event PropertyChangedEventHandler? PropertyChanged;
}
Was wondering if anyone can help me out? Maybe I'm missing something that I just can't identify.
Silly me, I was invoking the propertychanged event before the change of the actual value. The correct way is just to swap that:
public class EntryLog : INotifyPropertyChanged
{
private DateTime _date;
public DateTime Date
{
get => _date;
set
{
if (_date != value)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Date)));
_date = value;
}
}
}
public string Description { get; set; }
public double Hours { get; set; }
public event PropertyChangedEventHandler? PropertyChanged;
}

Clear entry text from ViewModel using RelayCommand

I would like to clear entry text from my ViewModel which is binded there. In the code below I tried it by using a RelayCommand, but it doesn't work.
What i want to accomplish: When clicking button named AddQuestionToQuiz, a function is executed by using Command on the button. The function OnCreateQuizClick(), located in my ViewModel, is triggerd and this function needs to clear my entry text, which i don't get for the moment.
I also tried to use a regular Command instead of using a RelayCommand, but also here it doesn't want to work.
EDIT: UNDERNEATH CODE WORKS FINE - GOT UPDATED
Code is used to clear entry text when clicking on a button from your ViewModel, implementing INotifyPropertyChanged Interface
.xaml - code
<Button x:Name="AddQuestionToQuiz" WidthRequest="200" Command="{Binding CreateQuizCommand}" Style="{StaticResource ButtonStyle}" Text="Add question to quiz"></Button>
ViewModel - code
internal class CreateQuizPageViewModel : INotifyPropertyChanged
{
// Quiz Name Input
public String QuizNameInput { get; set; }
private String quizQuestionInput = "";
public String QuizQuestionInput
{
get { return quizQuestionInput; }
set { quizQuestionInput = value; OnPropertyChanged(); }
}
public RelayCommand CreateQuizCommand { get; set; }
public CreateQuizPageViewModel()
{
CreateQuizCommand = new RelayCommand(OnCreateQuizClick);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnCreateQuizClick()
{
QuizQuestionInput = "";
}
}
EDIT: VIEWMODEL UPDATED
.xaml - code
<Button x:Name="AddQuestionToQuiz" WidthRequest="200" Command="{Binding CreateQuizCommand}" Style="{StaticResource ButtonStyle}" Text="Add question to quiz"></Button>
ViewModel - code
internal class CreateQuizPageViewModel : INotifyPropertyChanged
{
// Quiz Name Input
public String QuizNameInput { get; set; }
private String quizQuestionInput = "";
public String QuizQuestionInput
{
get { return quizQuestionInput; }
set { quizQuestionInput = value; OnPropertyChanged(); }
}
public RelayCommand CreateQuizCommand { get; set; }
public CreateQuizPageViewModel()
{
CreateQuizCommand = new RelayCommand(OnCreateQuizClick);
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnCreateQuizClick()
{
QuizQuestionInput = "";
}
}

How to save piker's value to Realm db?

i am working with Realm and my example is similar to this one by #BenBishop . I added a Yes / No picker to my PersonPage and im trying to save the selected value of the picker to my db. Could anyone help me with that please? Thanks!
This is not really a Realm question as that sample just uses Realm internally for storage. Most of its code is generic C# Xamarin code.
As a quick summary, you need to
Add a property in AddEditPersonViewModel.cs
Update AddEditPersonViewModel.Init to load the new property
Add a picker mapped to that property, in PersonPage.xaml
Add a matching property in Person.cs to store that value
Update IDBService.SavePerson to allow passing the new property
Update RealmDBService.SavePerson to copy the new property back to Realm
In detail:
// step 1 & 2
public class AddEditPersonViewModel : INotifyPropertyChanged
{
...
// added property
private int superPower;
public int SuperPower {
get {
return superPower;
}
set {
superPower = value;
PropertyChanged(this, new PropertyChangedEventArgs("SuperPower"));
}
}
...
public void Init (string id)
{
...
SuperPower = Model.SuperPower;
// step 3 - in PersonPage.xaml
Text="{Binding LastName}" />
<Picker SelectedIndex="{Binding SuperPower}">
<Picker.Items>
<x:String>Flight</x:String>
<x:String>Super Strength</x:String>
<x:String>Ordinariness</x:String>
</Picker.Items>
</Picker>
</StackLayout>
// step 4 - in Person.cs
public class Person : RealmObject
{
...
public int SuperPower {
get;
set;
}
// step 5 in IDBService.cs
public interface IDBService
{
bool SavePerson (string id, string firstName, string lastName, int SuperPower);
// step 6 in RealmDBService.cs
public class RealmDBService : IDBService
{
...
public bool SavePerson (string id, string firstName, string lastName, int superPower)
{
try {
RealmInstance.Write (() => {
var person = RealmInstance.CreateObject<Person> ();
person.ID = id;
person.FirstName = firstName;
person.LastName = lastName;
person.SuperPower = superPower;
});

Caliburn Action not firing

<ItemsControl DockPanel.Dock="Right" x:Name="Actions">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="Action"
HorizontalAlignment="Right"
Content="{Binding Label}"
Margin="3" Width="30"></Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
The above view binds with this viewmodel
public class DeploymentInputViewModel<T> : PropertyChangedBase
{
public BindableCollection<InputActionViewModel> Actions {get;set;}
}
I see my buttons. But when clicking it nothing happen.
The viewModels for InputActionViewModel:
public abstract class InputActionViewModel{
public InputActionViewModel()
{
}
public virtual Task Action()
{
return Task.FromResult<object>(null);
}
public string ActionToolTip { get; set; }
public string Label { get; set; }
public object Value { get; set; }
}
and also
public class InputCertificateActionViewModel : InputActionViewModel
{
[Import]
private IShell _shell;
[Import]
private IWindowsDialogs _dialogs;
private readonly IDeploymentSettingInputViewModel vm;
public InputCertificateActionViewModel(IDeploymentSettingInputViewModel vm)
{
this.vm = vm;
Label = "...";
ActionToolTip = "Pick a Certificate";
}
public bool IsManagementCertificate {get;set;}
public bool IsDeploymentCertificate { get; set; }
public async override Task Action()
{
if(IsManagementCertificate)
{
var subs = await _shell.IdentityModel.GetEnabledSubscriptionsAsync();
foreach(var sub in subs)
{
using (ManagementClient client = CloudContext.Clients.CreateManagementClient(sub.GetCredentials()))
{
var cert = _dialogs.SelectItemDialog("Select a certificate", "Pick one", true,
(await client.ManagementCertificates.ListAsync()).Select(c =>
new SelectItem(c.Thumbprint, Encoding.Default.GetString(c.PublicKey), c, (s) => c.Thumbprint.Contains(s))).ToArray())
.Tag as ManagementCertificateListResponse.SubscriptionCertificate;
this.vm.Value = cert.Thumbprint;
}
}
}else if(IsDeploymentCertificate)
{
}
}
}
I am adding actionViewModels by inserting directly into the observable code at startup.
haveActions.Actions.Add(DI.BuildUp(new InputCertificateActionViewModel(vm)
{
IsDeploymentCertificate = certAttribute.IsDeploymentCertificate,
IsManagementCertificate = certAttribute.IsManagementCertificate,
}));
haveActions is an instance of InputCertificateActionViewModel
Couldn't fit this all in a comment:
I can't have a peek at the Caliburn.Micro at the moment, but it might be something related to calling your method Action.
At a guess though, I'd say that by convention Caliburn.Micro expects to find a method that matches the Action<T> delegate to use for it's Actions, so your public virtual Task Action() won't be located and bound.
Have a quick check by defining a new method with a compatible signature, e.g public void MyMethod() and checking to see if it's located correctly and will function.
If that is the problem, you'll probably want to have a look at the IResult and Coroutines part of the Caliburn.Micro documentation, which looks like it will help you implement your desired behaviour.

Windows Phone 7 - Performance issues with "Filter as you write"-feature

i have some performance problems implementing a feature, where a listbox is realtime-filtered while user is typing the filter-string to a textbox. Feature i'm trying to create is similar to the call history search in WP7.
I created a simple project to test this and copy-pasted the important bits below. Basically i have a TextBox, where user is supposed to write a string that will be used to filter the data bound to a listbox. This filtering should happen in realtime, not after tapping any sort of filter-button etc.
ListBox is bound to a CollectionViewSource which uses a ObservableCollection as a source. When something is entered to the textbox, that value is instantly databound to a property in view model. View model property's setter fires up the filtering of CollectionViewSource, which updates the ListBox's contents.
In the actual project i'm doing, the ListBox can contain a hundred or so items.
Here's the related XAML:
<TextBox TextChanged="TextBox_TextChanged" Text="{Binding FilterString, Mode=TwoWay, UpdateSourceTrigger=Explicit}"></TextBox>
<ListBox ItemsSource="{Binding ItemsListCVS.View, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Prop1, Mode=TwoWay}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind to trigger instant binding to ViewModel-property:
private void TextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
var textBox = sender as TextBox;
// Update the binding source
BindingExpression bindingExpr = textBox.GetBindingExpression(TextBox.TextProperty);
bindingExpr.UpdateSource();
}
ViewModel:
private ObservableCollection<AnItem> _itemsList = new ObservableCollection<AnItem>();
private CollectionViewSource _itemsListCvs = new CollectionViewSource();
public ObservableCollection<AnItem> ItemsList
{
get
{
return _itemsList;
}
set
{
_itemsList = value;
// Update bindings, no broadcast
RaisePropertyChanged(ItemsListPropertyName);
}
}
public string FilterString
{
get
{
return _filterString;
}
set
{
if (_filterString == value)
{
return;
}
_filterString = value;
// Update bindings, no broadcast
RaisePropertyChanged(FilterStringPropertyName);
this.Filter();
}
}
public CollectionViewSource ItemsListCVS
{
get
{
return _itemsListCvs;
}
set
{
if (_itemsListCvs == value)
{
return;
}
_itemsListCvs = value;
// Update bindings, no broadcast
RaisePropertyChanged(ItemListPropertyName);
}
}
public MainViewModel()
{
var items = Builder<AnItem>.CreateListOfSize(100).Build();
this.ItemsList = new ObservableCollection<AnItem>(items);
this.ItemsListCVS.Source = this.ItemsList;
}
private void Filter()
{
this.ItemsListCVS.View.Filter = r =>
{
if (r == null) return true;
return ((AnItem)r).Prop1.ToString().ToLowerInvariant().Contains(FilterString);
};
}
AnItem-class which is databound to the list:
public class AnItem
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
public string Prop4 { get; set; }
public string Prop5 { get; set; }
}
Question:
Everything is working okay, but there is a horrific lag between writing to the TextBox and updating of the ListBox.
Am i simply doing it wrong? If so, then how should i change my approach? I think that this is quite common requirement, so there's probably some nice solution for it.
Rather than rolling your own filter, could you use the AutoCompleteBox from the toolkit?
As an alternative, could you categorise the data and make it searchable via a LongListSelector?
Ultimately, if you've got poorly performing code you should use the Profiler to see where the actual bottleneck is. http://msdn.microsoft.com/en-us/library/hh202934(v=vs.92).aspx

Resources