Xamarin forms, how to save byte[] to SQLite database? - c#

today I am dealing with issue how to save byte array to SQLite database. When I am saving base[] to databse, it's looks like that property holding that array, but actually it is not save to database, when I restart application on device. Maybe I have to convert byte[] to base 64 before putting it to database? If yes, how I can do that? Also maybe there are another way save to sqlite database , without converting to base64?
This is my object class:
public class Unit
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public bool IsStarted { get; set; }
public byte[] CImage { get; set; }
}
Here I am selecting image from gallery and set to bindable value:
IGalleryImageService galleryService = Xamarin.Forms.DependencyService.Get<IGalleryImageService>();
galleryService.ImageSelected += (o, imageSourceEventArgs) =>
{
ActiveP.CImageBindable = imageSourceEventArgs.ImageSource;
MemoryStream st = new MemoryStream(imageSourceEventArgs.ImageSource);
ImageSource imgSource = ImageSource.FromStream(() => st);
(ActiveP.Page as PTemplate).CImage.Source = imgSource;
};
galleryService.SelectImage();
here is my other Unit class where I am updating my database or inserting objects to database:
private void UpdateObjectToDB()
{
PUnit pUinit = new PUnit()
{
Id = this.Id,
IsStarted = this.IsStarted,
CImage = this.CImage
};
App.database.Update(pUinit);
}
public int InsertObjectToDB()
{
PUnit pUnit = new PUnit()
{
IsStarted = this.IsStarted,
CCImage = this.CImage
};
return App.database.AddItem(pUnit);
}
Where I have to convert and how to implement that? Thank you for answers or suggestions.

Well, I solved issue by changing this class property to string type:
public class Unit
{
[PrimaryKey, AutoIncrement]
public string CImageBase64 { get; set; }
}
Then in other unit class I changed bindable data type:
public string CImageBindable
{
get
{
return base.CImageBase64;
}
set
{
base.CImageBase64 = value;
OnPropertyChanged(nameof(CImageBindable));
}
}
And here I converted to base 64
if (this.P.CImageBindable == null)
{
CImage.Source = "img.png";
}
else
{
var imageBytes = Convert.FromBase64String(this.P.CImageBindable);
MemoryStream st = new MemoryStream(imageBytes);
ImageSource imgSource = ImageSource.FromStream(() => st);
CImage.Source = imgSource;
}
And picture selection converted as well:
IGalleryImageService galleryService = Xamarin.Forms.DependencyService.Get<IGalleryImageService>();
galleryService.ImageSelected += (o, imageSourceEventArgs) =>
{
ActiveParking.CImageBindable = Convert.ToBase64String(imageSourceEventArgs.ImageSource);
MemoryStream st = new MemoryStream(imageSourceEventArgs.ImageSource);
ImageSource imgSource = ImageSource.FromStream(() => st);
(ActiveParking.Page as PTemplate).CImage.Source = imgSource;
};
galleryService.SelectImage();

You can try this
private void UpdateObjectToDB()
{
PUnit pUinit = new PUnit()
{
Id = this.Id,
IsStarted = this.IsStarted,
CImage = Encoding.ASCII.GetBytes(this.CImage)
};
App.database.Update(pUinit);
}
public int InsertObjectToDB()
{
PUnit pUnit = new PUnit()
{
IsStarted = this.IsStarted,
CCImage = Encoding.ASCII.GetBytes(this.CImage)
};
return App.database.AddItem(pUnit);
}

Related

EF6 duplicate entries on related entities

Ok, so I'm really frustrated with the duplication issues of EF6. I have searched the forum extensively and tried different things, but I can't seem to figure this one out.
My base model is a trainingprogram, that consists of many blocks, which in turn hold a list of circuits. Every circuit holds a number of exercisesets and progressions for each exercise. All the relevant parts of the model are in the minimal workin example below.
I have a controller that I use to persist training programs to the DB. Whenever I call dbcontext.SaveChanges(), my database i populated with duplicates of the related entities, although I have tried setting the state to unchanged, attaching the enities to the context and what not. Pretty much everything gets duplicated - exercises, categories and progressionschemes.
This is really a blocker, so any help is appreciated.
[HttpPost]
[Route("Add")]
public HttpResponseMessage AddProgram(TrainingProgram program)
{
int id = -1;
try
{
using (var dbcontext = new ApplicationDbContext())
{
foreach (TrainingBlock b in program.Blocks)
foreach (ExercisePairing c in b.Circuits)
{
foreach (ProgressionScheme ps in c.Progressions.Select(x => x.Progression).Distinct().ToList())
{
var ep = dbcontext.Progressions.FirstOrDefault(p => p.Name == ps.Name && p.Id == ps.Id);
if (ep is null)
dbcontext.Progressions.Attach(ps);
//dbcontext.Entry(ps).State = EntityState.Unchanged;// Attach(ps);
}
foreach (Exercise s in c.Sets.Select(x => x.Exercise).Distinct().ToList())
{
//dbcontext.Entry(s).State = EntityState.Unchanged;
//dbcontext.Entry(s.Category).State = EntityState.Unchanged;
dbcontext.ExerciseCategories.Attach(s.Category);
dbcontext.Exercises.Attach(s);
}
}
dbcontext.TrainingPrograms.AddOrUpdate(program);
dbcontext.SaveChanges();
id = program.Id;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
return Request.CreateResponse(HttpStatusCode.OK, id);
}
public class ExercisePairing:BaseClass
{
private List<ExerciseSet> sets = new List<ExerciseSet>();
private List<ProgressionForExercise> progressions = new List<ProgressionForExercise>();
private int id;
...
...
}
}
public class ProgressionForExercise:BaseClass
{
public int Id { get; set; }
private Exercise exercise;
public int ExerciseId { get; set; }
public int ProgressionId { get; set; }
[ForeignKey("ExerciseId")]
public Exercise Exercise
{
get => exercise;
set
{
exercise = value;
ExerciseId = exercise.Id;
}
}
private ProgressionScheme progression;
[ForeignKey("ProgressionId")]
public ProgressionScheme Progression
{
get =>progression ;
set
{
progression = value;
ProgressionId = progression.Id;
}
}
}
public class Exercise : BaseClass
{
private int id;
private string name;
private string description;
private string videourl;
private ExerciseCategory category;
public int CategoryId { get; set; }
[ForeignKey("CategoryId")]
public ExerciseCategory Category
{
get => category;
set
{
category = value;
OnPropertyChanged();
}
}
public int Id
{
get => id;
set
{
id = value;
OnPropertyChanged();
}
}
...
...
}
public class ExerciseCategory:BaseClass
{
private string name;
private int id;
[Key]
public int Id
{
get => id;
set
{
id = value;
OnPropertyChanged();
}
}
public string Name
{
get => name;
set
{
name = value;
OnPropertyChanged();
}
}
...
...
}
public class ProgressionScheme:BaseClass
{
private int id;
private string name;
private string description;
public int Id
{
get => id;
set
{
id = value;
OnPropertyChanged();
}
}
public string Name
{
get => name;
set
{
name = value;
OnPropertyChanged();
}
}
...
...
}
EDIT
I now solved the issue by basically re-wrtiting my controller code like so:
[HttpPost]
[Route("Add")]
public HttpResponseMessage AddProgram(TrainingProgram program)
{
int id = -1;
try
{
using (var dbcontext = new ApplicationDbContext())
{
TrainingProgram tp = new TrainingProgram();
tp.Title = program.Title;
tp.Remarks = program.Remarks;
tp.CreationDate = program.CreationDate;
tp.Creator = program.Creator;
tp.Blocks = new List<TrainingBlock>();
foreach (TrainingBlock b in program.Blocks)
{
TrainingBlock tb = new TrainingBlock();
tb.Title = b.Title;
tb.Remarks = b.Remarks;
tb.Circuits = new List<ExercisePairing>();
foreach (ExercisePairing c in b.Circuits)
{
ExercisePairing ep = new ExercisePairing();
ep.Sets = new List<ExerciseSet>();
foreach(ExerciseSet s in c.Sets)
{
ExerciseSet es = new ExerciseSet();
Exercise ex = dbcontext.Exercises.Find(s.Exercise.Id);
es.Exercise = ex;
ExerciseCategory ec = dbcontext.ExerciseCategories.Find(s.Exercise.Category.Id);
es.Exercise.Category = ec;
es.Reps = s.Reps;
es.Intensity = s.Intensity;
es.Unit = s.Unit;
ep.Sets.Add(es);
}
ep.Progressions = new List<ProgressionForExercise>();
foreach (ProgressionForExercise pro in c.Progressions)
{
Exercise ex = dbcontext.Exercises.Find(pro.Exercise.Id);
ProgressionScheme ps = dbcontext.Progressions.Find(pro.Progression.Id);
ProgressionForExercise p4e = new ProgressionForExercise();
p4e.Exercise = ex;
p4e.Progression = ps;
ep.Progressions.Add(p4e);
}
tb.Circuits.Add(ep);
}
tp.Blocks.Add(tb);
}
dbcontext.TrainingPrograms.AddOrUpdate(tp);
dbcontext.SaveChanges();
id = tp.Id;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
return Request.CreateResponse(HttpStatusCode.OK, id);
}
So in essence, I am avoiding the duplicates by only using the very objects that are present in the Context. Although this works, I am not sure if I am happy with that. If a simple CRUD peration takes that much code, I fail to see the upside of using EF VS simply using an SQL insert statement, which would be much more efficient in this case IMHO. Also, why does EF ignore the primary keys and insist on using its wn instances of my enitities? All of this is very strange.

Checking if data exist in sqlite-net before inserting them to database

Im making an Xamarin.Android application that will use "templates" , template is this:
[Table("Templates")]
public class Template
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int Category { get; set; }
//[TextBlob("imagesBlobbed")]
[OneToMany, Unique]
public List<TemplateImage> TemplateImages { get; set; }
public string ImagesHash { get; set; }
//public string imagesBlobbed { get; set; }
}
[Table("TemplateImages")]
public class TemplateImage
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int Category { get; set; }
public string ImagesHash { get; set; }
public int Image { get; set; }
[ForeignKey(typeof(Template))]
public int TemplateId { get; set; }
}
i want all the TemplateImages objects to be unique in my database, the attribute Unique doesnt do anything, cause i guess because TemplateImage table has an auto increment Id will always be unique, no matter if the Image or ImageHash is the same in 2 records.
I was thinking then how else i can be sure that TemplateImages will be unique (Notice: when i say unique i mean that there isn't any other List that have the exact Image for every TemplateImage, in the same order).
Also i was using the ResourceID of the images, as image, which is wrong cause they might change on every new compiled app update.
So i decided to make a hash md5 from the images. They only way that i have find to load resource images (my images are vector .xml files) in Xamarin.Android is by converting the resource to bitmap, and then convert the bitmap to byte, and then the byte to md5.
And also i should have a hash string for the template item too. so im creating an md5 hash for the template by combining all the byte[] of the images into one and then hasing that.
I create this crazy code for this job:
public static string GetMD5Hash(byte[] content)
{
using (var md5 = MD5.Create())
{
byte[] computedHash = md5.ComputeHash(Encoding.UTF8.GetBytes(BitConverter.ToString(content).Replace("-", "")));
return new System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary(computedHash).ToString();
}
}
private static SQLiteConnection instance;
public static SQLiteConnection db()
{
if (instance == null)
instance = new SQLiteConnection(System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "TemplatesData.db"));
return instance;
}
public static void CloseConnection()
{
if (instance != null)
{
instance.Close();
instance.Dispose();
instance = null;
}
}
}
public class TemplateDB
{
public static byte[] ConcatByteList(List<byte[]> list)
{
return list
.SelectMany(a => a)
.ToArray();
}
public static Bitmap GetBitmapFromVectorDrawable(Context context, int drawableId)
{
Drawable drawable = ContextCompat.GetDrawable(context, drawableId);
if (Build.VERSION.SdkInt < Android.OS.BuildVersionCodes.Lollipop)
{
drawable = (DrawableCompat.Wrap(drawable)).Mutate();
}
Bitmap bitmap = Bitmap.CreateBitmap(drawable.IntrinsicWidth,
drawable.IntrinsicWidth, Bitmap.Config.Argb8888);
Canvas canvas = new Canvas(bitmap);
drawable.SetBounds(0, 0, canvas.Width, canvas.Height);
drawable.Draw(canvas);
return bitmap;
}
public byte[] DrawableToByteArray(int resourceId)
{
var context = AppState.ApplicationState;
using (var bitmap = GetBitmapFromVectorDrawable(context, resourceId))
{
int size = bitmap.ByteCount;
byte[] byteArray = new byte[size];
ByteBuffer byteBuffer = ByteBuffer.Allocate(size);
bitmap.CopyPixelsToBuffer(byteBuffer);
byteBuffer.Rewind();
byteBuffer.Get(byteArray);
return byteArray;
}
}
public static void AddTemplate(int category, List<int> images)
{
var templateDB = new TemplateDB();
var imageByteList = new List<byte[]>();
foreach (int image in images)
{
imageByteList.Add(templateDB.DrawableToByteArray(image));
}
var tmpl = new Template()
{
Category = category,
};
var img1 = new TemplateImage()
{
Category = category,
Image = images[0],
ImagesHash = DatabaseHelper.GetMD5Hash(imageByteList[0]),
};
var img2 = new TemplateImage()
{
Category = category,
Image = images[1],
ImagesHash = DatabaseHelper.GetMD5Hash(imageByteList[1]),
};
var img3 = new TemplateImage()
{
Category = category,
Image = images[2],
ImagesHash = DatabaseHelper.GetMD5Hash(imageByteList[2]),
};
var img4 = new TemplateImage()
{
Category = category,
Image = images[3],
ImagesHash = DatabaseHelper.GetMD5Hash(imageByteList[3]),
};
var img5 = new TemplateImage()
{
Category = category,
Image = images[4],
ImagesHash = DatabaseHelper.GetMD5Hash(imageByteList[4]),
};
tmpl.TemplateImages = new List<TemplateImage>() { img1, img2, img3, img4, img5 };
tmpl.ImagesHash = DatabaseHelper.GetMD5Hash(ConcatByteList(imageByteList));
var result = DatabaseHelper.db().Query<TemplateImage>("Select * from Templates where ImagesHash=?", tmpl.ImagesHash);
if (result.Count == 0)
{
DatabaseHelper.db().InsertAll(tmpl.TemplateImages);
DatabaseHelper.db().Insert(tmpl);
DatabaseHelper.db().UpdateWithChildren(tmpl);
}
}
Which all of a sudden, gives out of memory exception.
After thinking for a while that i should stop programming and start belly dancing, i think that since im giving in sqlite AddTemplate function a list of ResourceIds that i have to manually create (can't avoid it), then why not to give them my own string hash code and compare that to find if a record exist?
What is the correct approach?
In the end, i was thinking to use guid as extra id fields and add the parameter unique,in that case i could be sure which templates have been inserted, but i decided to use transactions. With transactions i can know that all my data is created or none and also save the creation into a variable in SharedPreferences and avoid recreating or even checking if the database exist.
For checking if the data is correctly updated and no recreation is needed i used this code:
public bool FirstRun { get; set; } = true;
public int DatabaseCreatedVersionOf { get; set; } = -1;
public override void OnCreate()
{
base.OnCreate();
}
public void UpdateDatabaseCreatedVersion()
{
DatabaseCreatedVersionOf = Preferences.Get(Settings.DatabaseCreatedVersionOfKey,
Settings.DatabaseCreatedVersionOfDefault);
}
public void CreateTemplateDB()
{
UpdateDatabaseCreatedVersion();
if (DatabaseCreatedVersionOf == -1)
TemplateDB.CreateDB();
FirstRun = false;
}
public Template GetTemplateById(int id)
{
if (FirstRun)
{
CreateTemplateDB();
FirstRun = false;
}
return TemplateDB.GetTemplate(id);
}
public List<Template> GetAllTemplates()
{
if (FirstRun)
{
CreateTemplateDB();
FirstRun = false;
}
return TemplateDB.GetAllTemplates();
}
Now all i have to do is call GetTemplateById or GetAllTemplates and if any creation is required, it will happen.

Xamarin C# - Object in List<> auto update themselves, how to update the list from them?

I'm working on xamarin solution for my Github repository. For the Pins project, I'm facing a problem. There is the thing, if you create a pin (aka CustomPin()), and then, you want to edit the location. Then the address name will change and same for the location if you change the address, the location will be create based on the address name. So from here, it's easy.
However, when the Pin address/location is changing, I would like my Map update itself. But because the List<> property doesn't changes, it doesn't update the map.
So, get a pointer to your parent list or to the map?
But this solution doesn't seems properly good for my use, I think that maybe, another best solution exist, but I don't know how to do..
You can compile the project, but there is three updates.
CustomPin
public class CustomPin : BindableObject
{
public static readonly BindableProperty AddressProperty =
BindableProperty.Create(nameof(Address), typeof(string), typeof(CustomPin), "",
propertyChanged: OnAddressPropertyChanged);
public string Address
{
get { return (string)GetValue(AddressProperty); }
set { SetValue(AddressProperty, value); }
}
private static async void OnAddressPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
(bindable as CustomPin).SetValue(LocationProperty, await CustomMap.GetAddressPosition(newValue as string));
}
public static readonly BindableProperty LocationProperty =
BindableProperty.Create(nameof(Location), typeof(Position), typeof(CustomPin), new Position(),
propertyChanged: OnLocationPropertyChanged);
public Position Location
{
get { return (Position)GetValue(LocationProperty); }
set { SetValue(LocationProperty, value); }
}
private static async void OnLocationPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
(bindable as CustomPin).SetValue(AddressProperty, await CustomMap.GetAddressName((Position)newValue));
Debug.WriteLine("private static async void OnLocationPropertyChanged(BindableObject bindable, object oldValue, object newValue)");
}
public string Name { get; set; }
public string Details { get; set; }
public string ImagePath { get; set; }
public uint PinSize { get; set; }
public uint PinZoomVisibilityMinimumLimit { get; set; }
public uint PinZoomVisibilityMaximumLimit { get; set; }
public Point AnchorPoint { get; set; }
public Action<CustomPin> PinClickedCallback { get; set; }
public CustomPin(Position location)
{
Location = location;
Name = "";
Details = "";
ImagePath = "";
PinSize = 50;
PinZoomVisibilityMinimumLimit = uint.MinValue;
PinZoomVisibilityMaximumLimit = uint.MaxValue;
AnchorPoint = new Point(0.5, 1);
PinClickedCallback = null;
}
public CustomPin(string address)
{
Address = address;
Name = "";
Details = "";
ImagePath = "";
PinSize = 50;
PinZoomVisibilityMinimumLimit = uint.MinValue;
PinZoomVisibilityMaximumLimit = uint.MaxValue;
AnchorPoint = new Point(0.5, 1);
PinClickedCallback = null;
}
public CustomPin()
{
Address = "";
Location = new Position();
Name = "";
Details = "";
ImagePath = "";
PinSize = 50;
PinZoomVisibilityMinimumLimit = uint.MinValue;
PinZoomVisibilityMaximumLimit = uint.MaxValue;
AnchorPoint = new Point(0.5, 1);
PinClickedCallback = null;
}
}
CustomMap | PS: Just add these three methods
#region
public async static Task<string> GetAddressName(Position position)
{
string url = "https://maps.googleapis.com/maps/api/geocode/json";
string additionnal_URL = "?latlng=" + position.Latitude + "," + position.Longitude
+ "&key=" + App.GOOGLE_MAP_API_KEY;
JObject obj = await CustomMap.GoogleAPIHttpRequest(url, additionnal_URL);
string address_name;
try
{
address_name = (obj["results"][0]["formatted_address"]).ToString();
}
catch (Exception)
{
return ("");
}
return (address_name);
}
public async static Task<Position> GetAddressPosition(string name)
{
string url = "https://maps.googleapis.com/maps/api/geocode/json";
string additionnal_URL = "?address=" + name
+ "&key=" + App.GOOGLE_MAP_API_KEY;
JObject obj = await CustomMap.GoogleAPIHttpRequest(url, additionnal_URL);
Position position;
try
{
position = new Position(Double.Parse((obj["results"][0]["geometry"]["location"]["lat"]).ToString()),
Double.Parse((obj["results"][0]["geometry"]["location"]["lng"]).ToString()));
}
catch (Exception)
{
position = new Position();
}
return (position);
}
private static async Task<JObject> GoogleAPIHttpRequest(string url, string additionnal_URL)
{
try
{
var client = new HttpClient();
client.BaseAddress = new Uri(url);
var content = new StringContent("{}", Encoding.UTF8, "application/json");
HttpResponseMessage response = null;
try
{
response = await client.PostAsync(additionnal_URL, content);
}
catch (Exception)
{
return (null);
}
string result = await response.Content.ReadAsStringAsync();
if (result != null)
{
try
{
return JObject.Parse(result);
}
catch (Exception)
{
return (null);
}
}
else
{
return (null);
}
}
catch (Exception)
{
return (null);
}
}
#endregion
MainPage.xaml.cs | PS: PCL part, just change the Constructor
public MainPage()
{
base.BindingContext = this;
CustomPins = new List<CustomPin>()
{
new CustomPin("Long Beach") { Name = "Le Mans", Details = "Famous city for race driver !", ImagePath = "CustomIconImage.png", PinZoomVisibilityMinimumLimit = 0, PinZoomVisibilityMaximumLimit = 150, PinSize = 75},
new CustomPin() { Name = "Ruaudin", Details = "Where I'm coming from.", ImagePath = "CustomIconImage.png", PinZoomVisibilityMinimumLimit = 75, PinSize = 65 },
new CustomPin() { Name = "Chelles", Details = "Someone there.", ImagePath = "CustomIconImage.png", PinZoomVisibilityMinimumLimit = 50, PinSize = 70 },
new CustomPin() { Name = "Lille", Details = "Le nord..", ImagePath = "CustomIconImage.png", PinZoomVisibilityMinimumLimit = 44, PinSize = 40 },
new CustomPin() { Name = "Limoges", Details = "I have been there ! :o", ImagePath = "CustomIconImage.png", PinZoomVisibilityMinimumLimit = 65, PinSize = 20 },
new CustomPin() { Name = "Douarnenez", Details = "A trip..", ImagePath = "CustomIconImage.png", PinZoomVisibilityMinimumLimit = 110, PinSize = 50 }
};
Debug.WriteLine("Initialization done.");
PinActionClicked = PinClickedCallback;
PinsSize = Convert.ToUInt32(100);
MinValue = 50;
MaxValue = 100;
InitializeComponent();
Debug.WriteLine("Components done.");
}
Maybe it's easy or maybe the way I said it's the only one, but I don't know how to update the pin on the map if a pin got edited, because finaly, it's still the same object, so the list doesn't change...
Thank for help !
Edit 1
Ok so, I made some changes however, it still doesn't work.. I mean my code work as I want, but calling PropertyChanged doesn't change anything...
I changed some things like List<CustomPin> is now ObservableCollection<CustomPin>.. Also I changed a bit the xaml part to that:
<control:CustomMap x:Name="MapTest" CustomPins="{Binding CustomPins}" CameraFocusParameter="OnPins"
PinSize="{Binding PinsSize, Converter={StaticResource Uint}}"
PinClickedCallback="{Binding PinActionClicked}"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
And my CustomPin is now like that:
public class CustomPin : BindableObject, INotifyPropertyChanged
{
/// <summary>
/// Handler for event of updating or changing the
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public static readonly BindableProperty AddressProperty =
BindableProperty.Create(nameof(Address), typeof(string), typeof(CustomPin), "",
propertyChanged: OnAddressPropertyChanged);
public string Address
{
get { return (string)GetValue(AddressProperty); }
set { SetValue(AddressProperty, value); }
}
private static void OnAddressPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
(bindable as CustomPin).SetAddress(newValue as string);
Debug.WriteLine("Address property changed");
}
private async void SetAddress(string address)
{
if (setter == SetFrom.None)
{
setter = SetFrom.Address;
SetLocation(await CustomMap.GetAddressPosition(address));
setter = SetFrom.None;
NotifyChanges();
}
else if (setter == SetFrom.Location)
{
setter = SetFrom.Done;
SetValue(AddressProperty, address);
}
}
private enum SetFrom
{
Address,
Done,
Location,
None,
}
private SetFrom setter;
private async void SetLocation(Position location)
{
if (setter == SetFrom.None)
{
setter = SetFrom.Location;
SetAddress(await CustomMap.GetAddressName(location));
setter = SetFrom.None;
NotifyChanges();
}
else if (setter == SetFrom.Address)
{
setter = SetFrom.Done;
SetValue(LocationProperty, location);
}
}
public static readonly BindableProperty LocationProperty =
BindableProperty.Create(nameof(Location), typeof(Position), typeof(CustomPin), new Position(),
propertyChanged: OnLocationPropertyChanged);
public Position Location
{
get { return (Position)GetValue(LocationProperty); }
set { SetValue(LocationProperty, value); }
}
private static async void OnLocationPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
(bindable as CustomPin).SetLocation((Position)newValue);
Debug.WriteLine("Location property changed");
}
private void NotifyChanges()
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Address)));
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Location)));
}
public string Name { get; set; }
public string Details { get; set; }
public string ImagePath { get; set; }
public uint PinSize { get; set; }
public uint PinZoomVisibilityMinimumLimit { get; set; }
public uint PinZoomVisibilityMaximumLimit { get; set; }
public Point AnchorPoint { get; set; }
public Action<CustomPin> PinClickedCallback { get; set; }
public CustomPin(Position location)
{
setter = SetFrom.None;
Location = location;
Name = "";
Details = "";
ImagePath = "";
PinSize = 50;
PinZoomVisibilityMinimumLimit = uint.MinValue;
PinZoomVisibilityMaximumLimit = uint.MaxValue;
AnchorPoint = new Point(0.5, 1);
PinClickedCallback = null;
}
public CustomPin(string address)
{
setter = SetFrom.None;
Address = address;
Name = "";
Details = "";
ImagePath = "";
PinSize = 50;
PinZoomVisibilityMinimumLimit = uint.MinValue;
PinZoomVisibilityMaximumLimit = uint.MaxValue;
AnchorPoint = new Point(0.5, 1);
PinClickedCallback = null;
}
public CustomPin()
{
setter = SetFrom.None;
Address = "";
Location = new Position();
Name = "";
Details = "";
ImagePath = "";
PinSize = 50;
PinZoomVisibilityMinimumLimit = uint.MinValue;
PinZoomVisibilityMaximumLimit = uint.MaxValue;
AnchorPoint = new Point(0.5, 1);
PinClickedCallback = null;
}
}
Finally, a call of PropertyChanged doesn't do anything.. Any idea?
Thank !
PS: Do not forget that the solution is available on my github repository
You should use INotifyPropertyChanged in your Pin implementation. This way, when you update some parameters, you notify the changes and can update the map.
Faced a similar issue a while ago, what solved it for me was changing the binding in the xaml from this:
CustomPins="{Binding CustomPins}"
to this:
CustomPins="{Binding CustomPins, Mode=TwoWay}"
I finally had an idea ! In Xamarin Forms, App can be acceeded from anywhere so, what I did is:
Create a method in your MainPage.xaml.cs which invoke a PropertyChanged event. You can do something like that so:
public void PinsCollectionChanged()
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CustomPins)));
Debug.WriteLine("Updated !!!");
}
Then, from your item (For me it's a CustomPin object) of your list, call this method by getting the current instance of the application. Take a look at the code to understand:
private void NotifyChanges()
{
(App.Current.MainPage as MainPage).PinsCollectionChanged();
}
PS: Do not forget to add using MapPinsProject.Page; in your object.
Hope it helps !

EF Use values in WPF listbox

I Have one table of data:
tblFeed
Id
Title
Content
And I populated a Listbox in my WPF application with this table.
I have the issue now of using the Id value for an event but the Id keeps returning 0.
Any Suggestions?
WCF
public List<Feed> GetFeed()
{
List<Feed> r = new List<Feed>();
List<Feed> e;
using (TruckDb db = new TruckDb())
e = db.Feed.Where(x => x.Id != null).ToList();
foreach (var a in e)
{
var feed = new Feed()
{
Id = a.Id,
Title = a.Title,
Content = a.Content
};
r.Add(feed);
}
return r;
}
WPF
public async Task LoadFeeds()
{
TruckServiceClient TSC = new TruckServiceClient();
try
{
List<ClientItems> feeditems = new List<ClientItems>();
if (lbFeed.Items.Count <= 0)
foreach (var item in await TSC.GetFeedAsync())
{
feeditems.Add(new ClientItems
{
FId = item.Id,
FTitle = item.Title,
FContent = item.Content
});
}
lbFeed.ItemsSource = (feeditems.ToArray());
lbFeed.DisplayMemberPath = "FTitle";
}
catch (Exception)
{
throw;
}
}
public class ClientItems
{
public int FId { get; set; }
public string FTitle { get; set; }
public string FContent { get; set; }
public override string ToString()
{
return FTitle;
}
}
Delete Event
WCF
private void bnFeedDel_Click(object sender, RoutedEventArgs e)
{
TruckServiceClient service = new TruckServiceClient();
service.DelFeedAsync(new FeedView
{
Id = lbFeed.SelectedIndex
});
}
WPF
public void DelFeed(FeedView feedview)
{
using (var result = new TruckDb())
{
var t = new Feed
{
Id = feedview.Id
};
result.Feed.Remove(t);
result.SaveChanges();
}
}
In your bnFeedDel_Click method you are doing this:
Id = lbFeed.SelectedIndex
I think this is your problem as you don't want to set Id to a SelectedIndex value but rather:
[EDIT after some discussion]
Set SelectedValuePath inside LoadFeeds:
lbFeed.SelectedValuePath = "FId";
And use SelectedValue instead of SelectedIndex:
private void bnFeedDel_Click(object sender, RoutedEventArgs e)
{
TruckServiceClient service = new TruckServiceClient();
service.DelFeedAsync(new FeedView
{
// Of course you may want to check for nulls etc...
Id = (int)lbFeed.SelectedValue;
});
}
Also, you should use DbSet.Attatch() before deleting a record:
public void DelFeed(FeedView feedview)
{
using (var result = new TruckDb())
{
var t = new Feed
{
Id = feedview.Id
};
result.Feed.Attatch(t);
result.Feed.Remove(t);
result.SaveChanges();
}
}

Silverlight 4.0 reading complex xml with linq

I'm stuck with following XML problem.
This is my XML file:
<POIs lastUsedId="9000010">
<POI id="9000010" name="München"><Latitude>48.139126</Latitude><Longitude>11.5801863</Longitude>
<Address>muenchen</Address><PhotoDescription>Hofbräuhaus</PhotoDescription>
<Photos directory="_x002F_pics"><PhotoFile>pic4poi_9000010-01.jpg</PhotoFile>
<PhotoFile>pic4poi_9000010-02.jpg</PhotoFile><PhotoFile>pic4poi_9000010-03.jpg</PhotoFile>
<PhotoFile>pic4poi_9000010-04.jpg</PhotoFile></Photos>
<InformationFile>infos\info4poi_9000010.txt</InformationFile></POI>
</POIs>
And here is my code to read the file:
XDocument doc = XDocument.Load(s);
lastID = Int32.Parse(doc.Root.Attribute("lastUsedId").Value.ToString());
CultureInfo cultureInfo = new CultureInfo("en-GB");
var pois = from res in doc.Descendants("POI")
select new
{
id = Int32.Parse(res.Attribute("id").Value.ToString()),
name = res.Attribute("name").Value.ToString(),
latitude = Double.Parse(res.Element("Latitude").Value, cultureInfo),
longitude = Double.Parse(res.Element("Longitude").Value, cultureInfo),
address = res.Element("Address").Value.ToString(),
photoDesc = res.Element("PhotoDescription").Value.ToString(),
photoDir = XmlConvert.DecodeName(res.Element("Photos").Attribute("directory").Value.ToString()),
photoFiles = from a in doc.Descendants("Photos")
select new
{
photo = a.Element("PhotoFile").Value.ToString()
},
info = res.Element("InformationFile").Value.ToString()
};
foreach (var poi in pois)
{
IEnumerable<string> pF = (poi.photoFiles as IEnumerable<string>);
List<string> photoFiles = null;
if(pF != null)
photoFiles = pF.ToList<string>();
AddPushpin(poi.id, poi.name, poi.latitude, poi.longitude, poi.address, poi.photoDesc, poi.photoDir, photoFiles, poi.info);
};
I'm unsure about the part with the PhotoFiles because I get an unknown Object error when I try to read the Pushpin.
This what my Pushpin looks like:
public class MyPushpin : Pushpin
{
public int ID { get; set; }
public string Address { get; set; }
public string PhotoDesc { get; set; }
public string PhotoDir { get; set; }
public List<string> PhotoFiles { get; set; }
public MyPushpin() { }
public MyPushpin(int id, string name, double latitude, double longitude, string address, string photoDesc, string photoDir, List<string> photoFiles, string info)
{
Location loc = new Location(latitude, longitude);
this.ID = id;
this.Location = loc;
this.Name = name;
this.Address = address;
this.PhotoDesc = photoDesc;
this.PhotoDir = photoDir;
this.PhotoFiles = photoFiles;
this.Tag = info;
}
public void Update(string name , string photoDesc, List<string> photoFiles, string info)
{
this.Name = name;
this.PhotoDesc = photoDesc;
this.PhotoFiles = photoFiles;
this.Tag = info;
}
public override string ToString()
{
return String.Format("{0} - {1}", this.ID, this.Location, this.Address, this.PhotoDesc, this.PhotoDir, this.Tag);
}
And that's the code how I would like to use the file info in the custom Pushpin:
public partial class Gallery : ChildWindow
{
List<string> pics = null;
public Gallery(MyPushpin currentPin)
{
InitializeComponent();
pics = currentPin.PhotoFiles;
Loaded += (a, b) => {
LoadImages();
};
}
private void OKButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = true;
Close();
}
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
Close();
}
private void LoadImages()
{
List<Picture> coll = new List<Picture>();
pics.ForEach(delegate(String url)
{
coll.Add(AddPicture("url"));
});
//coll.Add(AddPicture("/pics/pic4poi_9000010-01.jpg"));
//coll.Add(AddPicture("/pics/pic4poi_9000010-02.jpg"));
//coll.Add(AddPicture("/pics/pic4poi_9000010-03.jpg"));
//coll.Add(AddPicture("/pics/pic4poi_9000010-04.jpg"));
Preview.Source = new BitmapImage(
new Uri(
"/pics/pic4poi_9000010-01.jpg",
UriKind.Relative));
lbImage.ItemsSource = coll;
}
private Picture AddPicture(string path)
{
return new Picture
{
Href = new BitmapImage(
new Uri(
path,
UriKind.Relative))
};
}
private void lbImage_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Preview.Source = ((Picture)lbImage.SelectedItem).Href;
}
}
public class Picture
{
public ImageSource Href { get; set; }
THX for your time
Chau
Could you please describe the problem that you are having in more detail. Have you debugged into it, where does it fail, what is the exception? What do you expect it to do.
Off the top of my head, this code looks wrong:
photoFiles = from a in doc.Descendants("Photos")
select new
{
photo = a.Element("PhotoFile").Value.ToString()
},
Replace doc with res, because you want the child elements of the current element, not of the document. Also you can use Elements here instead of Descendants since they are direct children. Also since there are multiple photo files, try a SelectMany (from a ... from b ...):
photoFiles = from a in res.Elements("Photos")
from b in a.Elements("PhotoFile")
select new
{
photo = b.Value.ToString()
},

Categories