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;
});
Related
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;
}
I am new to Xamarin - and I'm encountering a problem. How can I display the sum of the values of a column in a label from SQLite?
Here is my code.
Model Budget
public class Budget
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int Money { get; set; }
}
SQLite Database + Method GetBudgets()
public class StatisticsService
{
static SQLiteAsyncConnection db;
static async Task Init()
{
if (db != null)
return;
// Get an absolute path to the database file
var databasePath = Path.Combine(FileSystem.AppDataDirectory, "MyApp.db");
db = new SQLiteAsyncConnection(databasePath);
await db.CreateTableAsync<Budget>();
}
public static async Task AddBudget(int money)
{
await Init();
var budget = new Budget
{
Money = money,
};
await db.InsertAsync(budget);
}
public static async Task<int> GetBudgets()
{
await Init();
int sumBudgets = await db.ExecuteScalarAsync<int>("SELECT SUM(Money) FROM Budget");
return sumBudgets;
}
}
ViewModel Code
int budgetMoney;
public int BudgetMoney { get => budgetMoney; set => SetProperty(ref budgetMoney, value); }
public AsyncCommand OpenAddBudget { get; }
public AsyncCommand ListBudget { get; }
public StatisticsViewModel()
{
OpenAddBudget = new AsyncCommand(Open);
ListBudget = new AsyncCommand(ListGetBudget);
}
async Task Open()
{
var route = "addBudgetPage";
await Shell.Current.GoToAsync(route);
}
async Task ListGetBudget()
{
budgetMoney = await StatisticsService.GetBudgets();
}
View Xaml
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.Statistics"
xmlns:viewmodels="clr-namespace:MyApp.ViewModels"
xmlns:model="clr-namespace:MyApp.Models">
<ContentPage.BindingContext>
<viewmodels:StatisticsViewModel/>
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding OpenAddBudget}"/>
</ContentPage.ToolbarItems>
<ContentPage.Content>
<StackLayout>
<Button Text="Reload" Command="{Binding ListBudget}"/>
<Label Text="{Binding BudgetMoney}"/>
</StackLayout>
</ContentPage.Content>
I don't get any errors, but when debugging I noticed that the variable sumBudget is always 0. Do I have something wrong in the syntax of SQLite?
public static async Task<int> GetBudgets()
{
await Init();
int sumBudgets = await db.ExecuteScalarAsync<int>("SELECT SUM(Money) FROM Budget");
return sumBudgets;
}
Unfortunately, I somehow do not get further. The goal should be that when I click on the button "Reload" the sum of the individual budgets are displayed in the label.
Thanks for your help!
Edit:
Call AddButton
public class AddBudgetViewModel : ViewModelBase
{
int money;
public int Money { get => money; set => SetProperty(ref money, value); }
public AsyncCommand SaveCommand { get; }
public AddBudgetViewModel()
{
SaveCommand = new AsyncCommand(Save);
}
async Task Save()
{
if (money == 0)
return;
await StatisticsService.AddBudget(money);
await Shell.Current.GoToAsync("..");
}
}
this is setting the private field budgetMoney, which does NOT call PropertyChanged
budgetMoney = await StatisticsService.GetBudgets();
instead, you should set the public property, which will call PropertyChanged
BudgetMoney = await StatisticsService.GetBudgets();
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;
}
I have an ObservableRangeCollection that consists of a few items. How can I conduct a search in the ViewModel of the Page?
Edit:
ObservableCollection in ViewModel:
private ObservableRangeCollection<Smetka> _smetki = new ObservableRangeCollection<Smetka>();
public ObservableRangeCollection<Smetka> Smetki
{
get { return _smetki; }
set { SetProperty(ref _smetki, value); }
}
And this is the CollectionView in the View:
<CollectionView ItemsSource="{Binding Smetki}" BackgroundColor="{DynamicResource SecondaryColor}"
VerticalScrollBarVisibility="Never" HorizontalScrollBarVisibility="Never"
SelectionMode="Single" SelectionChangedCommand="{Binding LaunchDetailPage}"
SelectedItem="{Binding SelectedSmetka}" Grid.Row="1">
</CollectionView>
I have been searching for a VM search example for a while now. I would really appreciate it if someone could provide me with one.
Edit 2:
This is what the app looks like normally.
When Search Criteria is given the ObservableRangleCollection should display only the Smetkas, who meet the Criteria.
After removing the Search Criteria it should display all the Smetkas, who meet the new criteria. If the String is empty or whitespace it should display all of them.
Do you mean you want to retrieve the item from your list ?
If yes,just use LINQ,for example:
public class PLU
{
public int ID { get; set; }
public string name { get; set; }
public double price { get; set; }
public int quantity {get;set;}
}
public static ObservableCollection<PLU> PLUList = new ObservableCollection<PLU>();
retrieve like:
PLU item = PLUList.Where(z => z.ID == 12).FirstOrDefault();
Update:
string filter = "9";//the filter you input
var searchItems = new ObservableCollection<Smetka>(Smetki.Where((smetka) => smetka.Id.Contains(filter))); // Assuming the property is Id.
then you could replace the Smetki with searchItems .
Lets say I have 100 documents with fields
Name
Age
Address
Now suppose my business model is change and I want to add new field call PhoneNumber.
How to add field PhoneNumber in all 100 documents ?
Is is possible to such stuff on NoSQL database?
You will have to write code to iterate all the documents to update, then actually update a new value in each one of them. Firestore has no similar command as "update tablename set x=y where ..." in SQL.
Is is possible to such stuff on NoSQL database?
Yes it is! Assuming you have a User model class that look like this:
public class User {
private String name;
private int age;
private String address;
private String phoneNumber; //Property that is newly added
public User() {}
public User(String name, int age, String address, String phoneNumber) {
this.name = name;
this.age = age;
this.address = address;
this.phoneNumber = phoneNumber;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public String getPhoneNumber() { return phoneNumber; }
public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; }
}
To actually add a new property and update it accordingly, you need to use setters. If you are setting the values directly onto the public fields, the setters are not mandatory.
How to add field PhoneNumber in all 100 documents?
As also #Doug Stevenson mentioned in his answer, to solve this, you need to iterate all the documents within your users collection. So please use the following lines of code:
db.collection("users").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : task.getResult()) {
User user = document.toObject(User.class);
user.setPhoneNumber("+1-111-111-111"); //Use the setter
String id = document.getId();
db.collection("users").document(id).set(user); //Set user object
}
}
}
});
The result of this code would be to add the phoneNumber property to all you User objects with a default value of +1-111-111-111. You can also set the value to null if it's more convenient for you. At the end, the updated object is set right on the corresponding reference.
If you are not using a model class, please see my answer from this post.