WPF Data binding and inventory ObservableCollection treatment - c#

Dear fellow enthusiasts (and professionals),
I am relatively new to WPF and novice in C#. I am trying to improve my understanding of these, and therefore working on a small application to replace the excel-based tools for inventory- and order management of the family company.
The code itself is pretty simple, so none of the objects require a lot of computation or memory.
The core of the inventory in this application is an ObservableCollection<ProductStack> inventory_hardcopy in a ProductInventory singleton class.
The ProductStack class has a ProductStackCount (int) property and a Prod (<Product>) property.
A Product object has name, buyprice, sellprice and deliveryCost properties.
When the user begins to record the details of a new order, the UI should load the inventory data. The problem is that any changes are immediately reflected in any ObservableCollection, even if the user eventually cancels the dialog window.
I estimate that reverting these changes depending on dialogresult would be way more complex than other solutions.
One such solution would be to clone the inventory_hardcopy to inventory, while depending on the user feedback it is possible to replace the original inventory_hardcopy with the altered clone. This can be implemented via a for cycle and a copy ctor in the ProductStack class. Considering the low complexity and short range of different product objects, these are O(n) processes, but duplications nevertheless.
My current approach is similar, in which, rather then cloning I reference all the products of the ProductStacks of the inventory_hardcopy in a temporary ObservableCollection, called inventory, then I copy the StackCounts of the inventory_hardcopy stacks to inventory (this time the order of the stacks matches, therefore the process is O(n)). Finally, when the dialogresult is true, I copy back the ProductStackCounts to the inventory_hardcopy from inventory via an O(n2) process (O(n2), since the temporary collection can be reordered through drag-drop, therefore the corresponding ProductStack needs to be looked up before overwriting quantities).
However, to make this work, initiating an inventory must be done "manually" by calling OpenInventory() method of the ProductInventory class, and overwriting inventory_hardcopy must be done through calling SaveAndCloseInventory() of the same class.
My question is: is there a more elegand/more appropriate way to make this?
My product code fragments:
public sealed class ProductInventory
{
public ObservableCollection<ProductStack> Inventory
{
get { return inventory; }
set { inventory = value; }
}
ObservableCollection<ProductStack> Inventory_hardcopy
{
get { return inventory_hardcopy; }
set { inventory_hardcopy = value; }
}
public void OpenInventory()
{
Inventory.Clear();
for (int i = 0; i < Inventory_hardcopy.Count; i++)
{
Inventory.Add(new ProductStack(Inventory_hardcopy[i].Prod, Inventory_hardcopy[i].ProductStackCount));
}
}
public void SaveAndCloseInventory()
{
for (int i = 0; i < Inventory.Count; i++)
{
int idx = 0;
while (idx < Inventory_hardcopy.Count && Inventory[i].Prod.ProductName != Inventory_hardcopy[idx].Prod.ProductName)
{
idx++;
}
Inventory_hardcopy[idx].ProductStackCount = Inventory[i].ProductStackCount;
}
// //write to disk here...
}
//other stuffs are implemented here...
}

Related

Trying to insert/sort a list based on 'Order' value

I am creating a documentation application in which I allow people inside my business to access documentation about certain software. With this, I have a page where any admin can manage categories and users. For this, I want the admin to be able to change the order of a category in which it should be displayed on my NavigationMenu. Now, the part where it should properly order the categories based on Order inside the NavigationMenu works. But when I try to edit existing categories and their order numbers, the orders won't update accordingly, see this example:
This is before editing existing categories, these categories are freshly added/made
This is after editing the categories, "React" should be Order 2, Where API would be Order 0, and Test would be Order 1
As you can see, the order doesn't make sense anymore. Obviously, there shouldn't be allowed more than 1 of any order.
Now, the problem is most likely coming from this code.
CategoryService.cs
public async Task<List<Category>> InsertCategory(Category category)
{
await GetCategories();
for (int i = 0; i < Categories.Where(c => c.Order >= category.Order).Count(); i++)
{
Categories[i].Order++;
if (Categories[i].Order == category.Order)
{
Categories[i].Order--;
break;
}
}
await categoryRepository.InsertAsync(Categories);
EventHelper.NotifyCategoryListChanged(Categories, EventArgs.Empty);
return Categories;
}
The order of the code goes like this:
CategoryDialog.razor
private async void SaveCategory(Category category)
{
if(!string.IsNullOrEmpty(category.Name))
{
await categoryService.SaveCategory(category);
Snackbar.Add("Category " + category.Name + " added", Severity.Success);
MudDialog.Close(DialogResult.Ok(category.Id));
}
else
{
Snackbar.Add("Please enter a category name.", Severity.Warning);
}
}
The above code is called after a button press. This passes along a category with a certain order number, this number gets passed along from a simple dropdown menu.
The SaveCategory function looks like this:
CategorySerice.cs
public async Task<Category> SaveCategory(Category category)
{
await InsertCategory(category);
if (categoryRepository.GetByIdAsync(category.Id) == null)
{
await categoryRepository.AddAsync(category);
}
else
{
await categoryRepository.SaveAsync(category);
}
EventHelper.NotifyCategoryListChanged(Categories, EventArgs.Empty);
return category;
}
This function calls the problematic function before actually saving/adding anything to the database. So it can take place for the newly added or edited category.
After this, an Event gets fired to notify my NavigationMenu that there have been changes made, and it should re-render to show this. This has no problems.
But I can't figure out how I would properly have the Orders be listed when I change them.
Quick reminder, this function doesn't work when editing existing categories. If I'd add a new category for example at order 2. It does properly shift everything with no problem.
Any help is welcome!
So if user tries to make a second with say order 2, it will reduce the order number of the one that was before?
Your loop logic doesn't support that, because your loop is increasing so by the time it happens you have already passed the previous so you'll get a double 1 presumably, after.
I think you'll get where you want if your change your loop to run downwards like your pushing down, though obviously you'll have to support order below zero soon and that is not always supported
TO:
for (int i = Categories.Count() -1; i >-1; i--)
{
if (Categories[i].Order <= category.Order)
{
Categories[i].Order = Categories[i].Order--;
}
}
But for everything to make sense and not running in to this i suggest pushing the order up instead, because positive high numbers do not have the same problems in the corners so to say
Or instead:
for (int i = 0; i < Categories.Count(); i++)
{
if(Categories[i].Order < category.Order) continue;
Categories[i].Order = Categories[i].Order++;
}
Ultimately also unless your implementation is different than i imagine, you will want to actually add the new category and not all of them (excluding thew new) again
--FROM: await categoryRepository.InsertAsync(Categories);
--TO:
await categoryRepository.InsertAsync(category);

PUN 2 Getting Custom Properties

I've recently taken on the task of custom properties in Photon. I have been able to figure out how to set the custom properties, but not get the custom properties. My hashtable is in my player controller script, while the place where I set (and where I want to get) properties is in a round loop script.
From RoundSystem:
private IEnumerator TeamBalance()
{
angelCount = Mathf.Floor(PhotonNetwork.PlayerList.Length * angelPercent);
currentAngels = angelCount;
currentPlayers = PhotonNetwork.PlayerList.Length;
foreach (var item in PhotonNetwork.PlayerList)
{
var itemPhotonView = (PhotonView)item.TagObject;
itemPhotonView.RPC("SetPlayerTeam", item, citiString);
}
for (int i = 0; i < angelCount;)
{
var item = PhotonNetwork.PlayerList[Random.Range(0, PhotonNetwork.PlayerList.Length)];
var itemPhotonView = (PhotonView)item.TagObject;
if (/* random player selected's, AKA, item's team == citiString */)
{
itemPhotonView.RPC("SetPlayerTeam", item, angelString);
i++;
}
}
yield return null;
//the reason this is in an IEnumerator with 'yield return null'
//is because I plan to add a waiting period once I figure this out
//it's for the game loop
}
From PlayerController:
[PunRPC]
public void SetPlayerTeam(string teamString)
{
//in the class: private ExitGames.Client.Photon.Hashtable playerProperties;
if (!playerProperties.ContainsKey("team"))
{
playerProperties.Add("team", teamString);
}
playerProperties["team"] = teamString;
PhotonNetwork.LocalPlayer.SetCustomProperties(playerProperties);
}
At the beginning of the round, a percentage (in this case 1/3) of players are chosen to be an "angel". The check here is needed because in cases of multiple angels, you don't want an already existing angel to count as a new change. (Also, it's probably important to known generally how to get custom properties if I'm going to be using them.) If I don't include the check in RoundSystem, the outcome is 2 citizens and 1 angel (in a test with 3 players). Also, if you see any spaghetti code that could be improved on, please don't hesitate to tell me. :)
Use Player.CustomProperties dictionary to access player's custom properties.
foreach (var item in PhotonNetwork.PlayerList)
{
if (item.CustomProperties.ContainsKey("team"))
{
Debug.Log(item.CustomProperties["team"]);
}
}
Also, the RoundSystem can implement IInRoomCallbacks interface and listen to OnPlayerPropertiesUpdate to catch the exact moment when the team gets updated. https://doc-api.photonengine.com/en/pun/v2/interface_photon_1_1_realtime_1_1_i_in_room_callbacks.html

Memory Leaks in Get / Set LINQ BindingList<T> Property

We have memory leaks in a program that we are trying to stop.
We have isolated the form, and this form has several BindingList Properties.
Currently, these properties are written in the following format:
private BindingList<DataModel1> _dataModel1List;
public BindingList<DataModel1> DataModel1List
{
get
{
var temp = from r in dbo_datamodel1 orderby r.label select r;
_dataModel1List= ((IListSource)temp).GetList() as BindingList<DataModel1>;
return _dataModel1List;
}
set
{
_dataModel1List= value;
}
}
I am no expert on the BindingList class, but it looks to me like we are getting a new set of data every time we call this.
Further, if we get the data using the getter and then set it later using the setter, it looks like we are going to overwrite what we had before with the incoming value.
Could this be the source of our memory leaks? I count 52 of these BindingList properties on this one form.
If I were to rewrite it like below, would that fix the memory leak or cause problems with the BindingList? I am particularly concerned with the Clear() call in the setter.
private BindingList<DataModel1> _dataModel1List;
public BindingList<DataModel1> DataModel1List
{
get
{
if (_dataModel1List== null)
{
_dataModel1List= new BindingList<DataModel1>(
(from r in dbo_datamodel1
orderby r.name
select r).ToList());
}
return _dataModel1List;
}
set
{
if (_dataModel1List!= null)
{
_dataModel1List.Clear();
}
_dataModel1List= value;
}
}
Since there are so many of these properties that could be contributing to the memory leak, I want to make sure I am not going to cause problems before I make this change all over.

An object in 2 Lists does not get found in one

I have some strange behaviour I can't explain:
I have a List of Ellipsis in a WPF-Canvas, which I'd like to make blink.
For performances purpose, I add newly created stars to the Canvas (which holds more objects) and to a local list. This means I can work just with the local List and dont have to recheck the Children-Collection every time.
My code is looking like this:
private const Int32 STAR_COUNT = 500;
private readonly double STAR_MOVE_RATE = GameObject.OBJECTS_MOVE_RATE/2;
private readonly Timer _blinkingTimer = new Timer();
private readonly Canvas _gameField;
private readonly List<Ellipse> _stars = new List<Ellipse>();
public StarHandler(Canvas gameField)
{
_gameField = gameField;
_blinkingTimer.Interval = 70;
_blinkingTimer.Elapsed += _blinkingTimer_Elapsed;
for (int i = 0; i < STAR_COUNT; i++)
AddSetStar(StarFactory.CreateStar());
_blinkingTimer.Start();
}
private void AddSetStar(Ellipse star)
{
Canvas.SetTop(star, GameObject.Random.Next(GameObject.LEVEL_HEIGHT));
Canvas.SetLeft(star, GameObject.Random.Next(GameObject.LEVEL_WIDTH));
Panel.SetZIndex(star, 0);
_gameField.Children.Add(star);
_stars.Add(star);
}
private void _blinkingTimer_Elapsed(object sender, ElapsedEventArgs e)
{
ThreadingHelper.ThreadingAction(() =>
{
Ellipse star = _stars.ElementAt(GameObject.Random.Next(STAR_COUNT));
_gameField.Children.Remove(star);
_stars.Remove(star);
var newStar = StarFactory.CreateStar();
AddSetStar(newStar);
});
}
The StarFactory just creates such an Ellipse and returns it.
What I dont get: The
_gameField.Children.Remove(star);
works only for the Stars I add in the Constructor. The Stars I add on the Timer are not found on the Children-List.
I really have no idea, how this Elements can not be found since I add newly created Stars everytime on both lists.
P.S.: The ThreadingAction is just a helper to work on the UI-Thread:
internal static void ThreadingAction(Action action)
{
if (Thread.CurrentThread == Application.Current.Dispatcher.Thread)
action();
else
Application.Current.Dispatcher.Invoke(action);
}
Edit: The reason why I'm holding two lists is this:
If I want to work with the List, I use:
_stars.ForEach(f =>
Without this, I'd have to
_gameField.Children.OfType<Ellipse>().ToList().ForEach(f => )
This is 2 Enumerations, one for OfType and one for ToList.
And what If I add other ellipses, which are not Stars?
About the Construction of the Stars: I do this only on two positions: In the Timer-Elapsed Event and in the Constructor. Does the Children-List not point to the objects then?
So the Stars are the same references, since I add the same reference everytime in both Lists.
You have a number of problems with your code. I shall attempt to address each in turn. First, you said that you
add newly created stars to the Canvas (which holds more objects) and to a local list. This means I can work just with the local List and dont have to recheck the Children-Collection every time
However, there is no benefit to doing this. The Children collection points to a certain location in memory and your collection points to another... virtually no difference.
What I dont get: The Children.Remove(star) works only for the Stars I add in the Constructor
That is because you can only remove an item from the collection if that exact item is in the collection. Therefore, you cannot generate a new object with certain property values and expect that to equal another object with identical property values... they are different objects in memory. So, to get around this, you can use LinQ to do something like this:
_gameField.Children.Remove(_gameField.Children[0]);
However, I believe that your final error is that you shouldn't be removing and re-adding these objects into the Children collection as you are anyway. Instead, it makes more sense to leave the objects in the Children collection and merely change their Visiblity values from Visible to Hidden and back repeatedly... perhaps something like this:
foreach (UIElement element in Canvas.Children)
{
element.Visibility = element.Visibility == Visibility.Hidden ?
Visibility.Visible : Visibility.Hidden;
}

Linked list Advice

I've been given a task as follows:
I'd like to to build an implementation of a Linked List. Specifically I'd like it to be a doubly linked list.
My task:
Your program should use the linked list to model a train route using the linked list.
First the user will enter as many stops as they'd like the train to have, and the name of each stop.
The program should then print a map of the route.
Once finished they then enter the name of the stop they want to start at.
From there they can enter commands to move the train either forward to the next stop or backward to the previous one.
I've been told I'm not doing this task right but I don't really understand how not, I'd appreciate it if someone could explain what I'm not doing that I should be doing.
My Route class (it isn't finished but it would've been nearly finished if it was done correctly):
namespace TrainRoute
{
class Route
{
Stops root;
public LinkedList<Stops> linkedList = new LinkedList<Stops>();
public Stops MakeNewStop(string stopName)
{
Stops stopWithStopName = new Stops(stopName);
return stopWithStopName;
}
public void AddStops(Stops stopIWantToAdd)
{
if (linkedList.Count == 0)
{
linkedList.AddFirst(stopIWantToAdd);
}
else
{
//stopIWantToAdd.prevStop = linkedList.Last();
linkedList.AddLast(stopIWantToAdd);
}
}
public void StopRelationships()
{
for (int i = 0; i < linkedList.Count; i++)
{
if (linkedList.ElementAt<Stops>(i).nextStop == null && linkedList.ElementAt<Stops>((i + 1)) != null)
{
linkedList.ElementAt<Stops>(i).nextStop = linkedList.ElementAt<Stops>((i + 1));
}
if (linkedList.ElementAt<Stops>((i - 1)) != null)
{
linkedList.ElementAt<Stops>(i).prevStop = linkedList.ElementAt<Stops>(i - 1);
}
}
}
public void Print()
{
if (linkedList != null)
{
foreach (var item in linkedList)
{
Console.WriteLine("Stop name: " + item.stopName);
}
}
}
public int StopPosition(string usersInput)
{
int position = 0;
for (int i = 0; i < linkedList.Count; i++)
{
if (linkedList.ElementAt<Stops>(i).stopName == usersInput)
{
position = i;
break;
}
}
return position;
}
public int MoveForward(int indexPosition)
{
Console.WriteLine("The train is now at " +linkedList.ElementAt<Stops>(indexPosition).nextStop.stopName);
return (indexPosition + 1);
}
public int MoveBackwords(int indexPosition)
{
Console.WriteLine("The train is now at " + linkedList.ElementAt<Stops>(indexPosition).prevStop.stopName);
return (indexPosition - 1);
}
public bool VerifyRoute(int indexPosition, string prevOrForward)
{
if (prevOrForward.Contains("forward"))
{
if (linkedList.ElementAt<Stops>((indexPosition+1)) != null)
{
return true;
}
}
else
{
if (linkedList.ElementAt<Stops>((indexPosition-1)) != null)
{
return true;
}
}
return false;
}
}
}
I'm also not allowed to use the Linked list class but I'm to use a linked list (I'm not 100% sure what that means).
Any and all advice/help provided will be appreciated!
Let's piece together the breadcrumbs here:
I'd like to to build an implementation of a Linked List.
and this:
I'm also not allowed to use the Linked list class
Obviously the task here is for you to implement your own linked list (class), and not to use the existing one provided by .NET.
I'm assuming here the task is not to build the program handling the trains, but instead to learn how a linked list works, and how you would go about implementing one.
As such, your shortcut to simply grab the existing class is the wrong tool for the job. It would be perfect (probably) if your task was to build that program, but in this case the program is orthogonal to your task, it's there to create a context for what you're really asked to do:
Implement your own version of LinkedList<T> (though you probably don't need to make it generic).
Wikipedia has a very good article on linked lists if you're stumped on how such a data structure really works. There's undoubtedly other very good resources out on the net as well, and probably in your text book or other resources.
Additionally, I would urge you to find a classmate to peer with, from experience I can say that most of the really hard problems I've had in my programming career has (usually) been solved by having a sparring partner to work with.
Implementing a linked list isn't that difficult. I assume you have a textbook and it discusses linked list, read it, carefully. Also you want to clarify with your tutor exactly how much functionality your linked list needs to implement.
Basically, you'll start with a node class, if you don't need it to be generic, then you can create a StopNode class. The basics of your node class will be a reference to the next node in the list and, since this is a doubly linked list, a reference to the previous node:
public class StopNode
{
public StopNode Next { get; set; }
public StopNode Previous { get; set; }
// whatever other properties your stop class needs - such as name
}
Now your LinkedList class will manage the collection of stop nodes. It will need to keep a reference to the first or "head" node and probably the last node too (or "tail").
public class StopLinkedList
{
private StopNode Head { get; }
private StopNode Tail { get; }
}
And it will need to implement methods to add and remove nodes (at a minimum) and probably also insert.
Add is pretty easy - check if Head is null. If it is, just set Head and Tail both equal to your new node. If it's not, you will instead set the Next property of your Tail to your new node, then set the Previous of your new node to the Tail and then finally update your Tail to reference your new node.
To remove a node, if given the node to remove, you will need to check it's Previous and Next properties and (assuming one or both isn't null - you'll need to add logic for that), you set your nodes Previous.Next to your nodes Next and your nodes Next.Previous to your nodes Previous. This will cause your node to fall out of the list (you can set your nodes Next and Previous to null if required, but it's not strictly necessary unless your removed node is going to hang around).
Hopefully that gets you started. Clarify with your tutor, check your textbook (probably better to try and match their terminology if it differs from mine) and also search for "linked list" and "doubly linked list" on the internet. You should find plenty of resources.

Categories