C
C#•2w ago
reeeeeee

Saving Draggable objects location wpf

I have like 10 different objects which can be draggable across canvas (textboxes, labels, WrapPanels, DockPanels, etc). I would like to save their location so it will be shown after the restart. What would be the best way to achieve that? Obviously i could do like 3 bindable properties for each object (vlaue, positionX, positionY), but that would be 30 properties which would be really messy and like 150 lines of code (with mvvvm binding). Any idea?
7 Replies
Clint
Clint•2w ago
I'd recommend setting up a base view model type that these items' view models can derive from, placing their positioning properties on that base type, and that way you don't have to worry about putting them on every class. You can then pretty easily have your collection where all those items are stored in a VM just be a collection of that base type, and implement your persist / reload logic at that level.
reeeeeee
reeeeeee•2w ago
So some kind of SpecialBindableObejct with at least 3 properties (value, PositionX, PositionY) and INotifyPropertyChanged And instead of binding 3 different variable, I can use only one, and do binding in xaml likeSpecialObject.PositionX, SpecialObject.PositionY, etc? or did you mean something else? Also, if I would like to store them into a json file and do loading from there like this
SpecialBindableObject MyTextBox1;
SpecialBindableObject MyTextBox2;
SpecialBindableObject Label1;
...
Dict<stirng, SpecialBindableObject> savedObjects = deserializeJsonContent from file
and then for each property something like:
MyTextBox1 = savedObjects["MyTextBox1"];
MyTextBox2= savedObjects["MyTextBox2"];
Label1= savedObjects["Label1"];
SpecialBindableObject MyTextBox1;
SpecialBindableObject MyTextBox2;
SpecialBindableObject Label1;
...
Dict<stirng, SpecialBindableObject> savedObjects = deserializeJsonContent from file
and then for each property something like:
MyTextBox1 = savedObjects["MyTextBox1"];
MyTextBox2= savedObjects["MyTextBox2"];
Label1= savedObjects["Label1"];
How ungly is that? 😅 i really have no idea how could i improve that and not make a mess, hah
Clint
Clint•2w ago
So let's say you have these items that users can drag around on a canvas, your ViewModels for each item will be unique to their own usecases, but they all share something in common, that is the fact they belong on the canvas and store positional data, so you could have your base view model type:
public sealed record Position(int X, int Y);
public sealed record Position(int X, int Y);
public abstract class CanvasItemViewModel {
public string Id { get; set; }
public Position Position { get; set; }
}
public abstract class CanvasItemViewModel {
public string Id { get; set; }
public Position Position { get; set; }
}
Omitted the INotifyPropertyChanged stuff for brevity. Then let's say you have a VM to represent your canvas container itself:
public class CanvasViewModel {
public List<CanvasItemViewModel> Items { get; set; }

public ImmutableDictionary<string, Position> SnapshotPositions() {
return Items.ToImmutableDictionary(x => x.Id, x => x.Position);
}
}
public class CanvasViewModel {
public List<CanvasItemViewModel> Items { get; set; }

public ImmutableDictionary<string, Position> SnapshotPositions() {
return Items.ToImmutableDictionary(x => x.Id, x => x.Position);
}
}
If you have additional serialisation / deserialisation needs per-item, e.g. having them store and load additional data relevant to them, then you'd want to come up with a more comprehensive mechanism for storing their own data, but this would be how I'd start to tackle the problem.
reeeeeee
reeeeeee•2w ago
Okay yeah, I had started with something similar and managed to make it work, which is nice. Thank you! (I should look into stuff like "sealed record", "abstract", "immutableDictionary") Haha ye, every new way of storing data ends totally ugly... well, the above way with hardcoding every property kinda works, maybe some better idea will pop in my mind anytime soon.. It technically works, except some properties have margin set in XAML and on fresh set of X and Y form file, their margin stays there and its location is actually wrong. An easy "fix" would be get rid of margins, but yea 😅 Ahh, just when I thought I already have a solution.. looks like I don't. One more question and then I'll let you go.. oh, when I was typing, this thought occured me,actually it might even work, will test it tommorow morning. If you are OK with that, can I ask you some questions if they will be needed?
Clint
Clint•2w ago
Yeah sure, not sure how rapidly I'll be able to answer but yeah, drop a message
reeeeeee
reeeeeee•2w ago
Okay, so I managed to do some "improvements", which include less hardcoding stuff. I have two models, CanvasPosition and StringCanvasPosition (this one inherits canvas position - some elements requires string, some int, so I just separated them.
public class CanvasPosition
{
public double? CanvasLeft { get; set; }

public double? CanvasTop { get; set; }
public string BindableName { get; set; }
}


public class StringCanvasPosition : CanvasPosition
{
public string TextValue { get; set; }
}
public class CanvasPosition
{
public double? CanvasLeft { get; set; }

public double? CanvasTop { get; set; }
public string BindableName { get; set; }
}


public class StringCanvasPosition : CanvasPosition
{
public string TextValue { get; set; }
}
Then I have bindable properties like this:
private StringCanvasPosition _receivedRequestsWrapper { get; set; }
public StringCanvasPosition ReceivedRequestsWrapper ....getter, setter...
private StringCanvasPosition _receivedRequestsWrapper { get; set; }
public StringCanvasPosition ReceivedRequestsWrapper ....getter, setter...
For the serialization and deserialization I used reflection. I am saving it to File and the code looks like this: Saving:
Dictionary<string, object> dict = new Dictionary<string, object>();
foreach (var property in this.GetType().GetProperties().Where(x => typeof(CanvasPosition).IsAssignableFrom(x.PropertyType)))
{
dict[property.Name] = property.GetValue(this);
}
var options = new JsonSerializerOptions
{
WriteIndented = true
};
var jsonString = JsonSerializer.Serialize(dict,options); ;
File.WriteAllText(file, jsonString);
Dictionary<string, object> dict = new Dictionary<string, object>();
foreach (var property in this.GetType().GetProperties().Where(x => typeof(CanvasPosition).IsAssignableFrom(x.PropertyType)))
{
dict[property.Name] = property.GetValue(this);
}
var options = new JsonSerializerOptions
{
WriteIndented = true
};
var jsonString = JsonSerializer.Serialize(dict,options); ;
File.WriteAllText(file, jsonString);
and loading is like this:
public async Task GetSettings() {
var fileContent = await File.ReadAllTextAsync("config.json");
var fds = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(fileContent);
foreach (var property in this.GetType().GetProperties().Where(x => typeof(CanvasPosition).IsAssignableFrom(x.PropertyType) && x.CanWrite))
{
if (fds.ContainsKey(property.Name) && typeof(CanvasPosition).IsAssignableFrom(property.PropertyType))
{
var jsonValue = fds[property.Name];
var value = JsonSerializer.Deserialize(jsonValue.GetRawText(), property.PropertyType);
property.SetValue(this, value);
}
}
}
public async Task GetSettings() {
var fileContent = await File.ReadAllTextAsync("config.json");
var fds = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(fileContent);
foreach (var property in this.GetType().GetProperties().Where(x => typeof(CanvasPosition).IsAssignableFrom(x.PropertyType) && x.CanWrite))
{
if (fds.ContainsKey(property.Name) && typeof(CanvasPosition).IsAssignableFrom(property.PropertyType))
{
var jsonValue = fds[property.Name];
var value = JsonSerializer.Deserialize(jsonValue.GetRawText(), property.PropertyType);
property.SetValue(this, value);
}
}
}
I would be a lot less messy if I could have only type of objects (but I will need more of them because I need some additional formatting before saving value to object - for exampe: string can only include numbers) Yeah, I know that reflection is usually better to be avoided, but this is currently the best idea I got, haha So.. what do you think about this? 😅
Clint
Clint•2w ago
Hmmmm, my recommendation would be instead to implement an interface something like
public interface IPersistable {
Dictionary<string, object> Persist();
void Load(Dictionary<string, object>> props);
}
public interface IPersistable {
Dictionary<string, object> Persist();
void Load(Dictionary<string, object>> props);
}
Then have your items implement it, this is sort of similar to how certain things in Apple's ecosystem work, so the idea being your individual types can pack and unpack their specific pieces of data, and you have a consistent interface to interact with that mechanism without having to resort to reflection. Also means you're not necessarily tied into JSON going forward either. You could also make it not use object and use JsonElement for example, but then System.Text.JSON contains primitives for making this slightly more workable like the JSON writer / reader utilities etc. Lots of ways to potentially solve this. Another of course would be to make your viewmodels derive from a persistable base, something like:
public abtract class PersistableViewModel<TStorage> {
abstract void Load(TStorage data);
abstract TStorage Persist();
}
public abtract class PersistableViewModel<TStorage> {
abstract void Load(TStorage data);
abstract TStorage Persist();
}
And then implementing it would be something like:
public sealed record TextBoxItemData(CanvasPosition Position, string StringValue);

public sealed class TextBoxViewModel : PersistableViewModel<TextBoxItemData> {
public override TextBoxItemData Persist() {
return new(this.Position, this.TextValue);
}

public override void Load(TextBoxItemData data) {
this.Position = data.CanvasPosition;
this.StringValue = data.StringValue;
}
}
public sealed record TextBoxItemData(CanvasPosition Position, string StringValue);

public sealed class TextBoxViewModel : PersistableViewModel<TextBoxItemData> {
public override TextBoxItemData Persist() {
return new(this.Position, this.TextValue);
}

public override void Load(TextBoxItemData data) {
this.Position = data.CanvasPosition;
this.StringValue = data.StringValue;
}
}
Then you have a well-defined data model for your persistance, that you can then ser/deser however you want, and is easy enough to just treat wholesale for your JSON storage. Your viewmodels can then be as complex as needed, and all you're worried about is upholding that in/out data contract, and the persistance code can take care of the rest.
Want results from more Discord servers?
Add your server