Linked list Advice - c#

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.

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);

nested foreach() iteration is mixing up target collections

I'm trying to write a script in space-engineers, and I have a collection of a class _Filter, which resides inside of another class _Inventory. Here is the code that I'm trying to execute:
public void InventorySetup(_Inventory inventory)
{
if (inventory.InvBlock != null) // Check if block exists
{
string[] data = inventory.InvBlock.CustomData.Split('\n'); // Break customData into lines
foreach (string nextline in data) // Iterate each line
{
if (nextline.Length > 0) // Line must contain information
{
string[] lineblocks = nextline.Split(' '); // Break each line into blocks
if (lineblocks.Length > 1) // There must be more than one block to have filter candidate and desired update
{
string[] itemID = new string[2];
if (lineblocks[0].Contains(":"))
itemID = lineblocks[0].Split(':'); // split the type from subType
else if (lineblocks[0].Contains("!"))
itemID = new string[] { "", "" };
else
itemID = new string[] { "null", "null" };
foreach (_Filter nextFilter in inventory.Filters)
{
if (ContainsCIS(nextFilter.ItemType, itemID[0]) && ContainsCIS(nextFilter.ItemSubType, itemID[1]))
{
for (int i = 1; i < lineblocks.Length; i++)
{
switch (lineblocks[i][0])
{
case '#':
nextFilter.Target = (MyFixedPoint)float.Parse(lineblocks[i].Replace('#', ' '));
break;
case '+':
if (ContainsCIS(lineblocks[i], "in"))
nextFilter.IN_BOUND = true;
if (ContainsCIS(lineblocks[i], "out"))
nextFilter.OUT_BOUND = true;
break;
case '-':
if (ContainsCIS(lineblocks[i], "in"))
// nextFilter.IN_BOUND = false;
if (ContainsCIS(lineblocks[i], "out"))
nextFilter.OUT_BOUND = false;
break;
}
}
}
}
}
if (nextline[0] == '&')
{
if (ContainsCIS(nextline, "empty"))
{
if (nextline.Contains("-"))
inventory.EMPTY = false;
else
inventory.EMPTY = true;
}
if (ContainsCIS(nextline, "fill"))
{
if (nextline.Contains("-"))
inventory.FILL = false;
else
inventory.FILL = true;
}
if (ContainsCIS(nextline, "active"))
{
if (nextline.Contains("-"))
inventory.ACTIVE = false;
else
inventory.ACTIVE = true;
}
if (ContainsCIS(nextline, "clean"))
{
if (nextline.Contains("-"))
inventory.CLEAN = false;
else
inventory.CLEAN = true;
}
if (ContainsCIS(nextline, "reset"))
inventory.Reset();
}
}
}
}
}
So basically what's going on, there's a member within the _Inventory class that refers directly to the block who's inventory I'm trying to configure. This member has it's own member called CustomData, which is a string object that can be edited in game. So I split it into an array of each line, and then process each line based on the contextual nature of each.
First, the line gets broken up into "blocks" by once again splitting each line by a space character, and then analyzing further. If the number of blocks is greater than 1, this means there is an expected "target filter" and some sort of following qualifier. Either a change to its white-list/black-list setting, or a change to the target maximum value (0 means no limit and is the default value for this member).
Now the _Inventory class already contains a pre-existing collection of _Filter class, and merely updates the members of it by means of iteration. So for example, if I want to black-list "IN_BOUND" ores, I would add the line "ore: -in". or if I want to black-list out bound steel plates, ":steelplate -out".
(a name before the colon depicts the category, and one after the colon, the specific type of item. Exclamation mark means all items in the collection). The ContainCIS() method is something I made simply to search for a contained string "Case In-Sensitively". Besides changing filters, the _Inventory class also possesses a few bool members for controlling desired functionality. They are self-named EMPTY, FILL, ACTIVE, CLEAN (the clean bool has to do with production blocks that sometimes get stuffed with the wrong materials, non-relevant to my problem)
My actual problem:
When this code is called, if I only have a single _Inventory in the root collection, everything works out fine. HOWEVER, if I have more than one, the bools are updated normally, but the filters GET UPDATED IN REVERSE ORDER.
So say for example I have _Inventory A, and _Inventory B. If I add the line to the custom data of the block that is referred to by "A", then the correct bool changes. But if I change a filter setting on "A", it updates to "B" instead.
EDIT:
Here's the classes and the method which calls the InventorySetup():
public class _Inventory
{
public IMyTerminalBlock InvBlock;
public _BlockType BlockType; // Cargo, Assembler, Refinery
public _Filter[] Filters; // Expected inventory candidates (Refer to _Filter libraries)
public bool FILL; // Attempt fill action on all IN_BOUND "true" candidates
public bool EMPTY; // Attempt empty action on all OUT_BOUND "true" candidates
public bool ACTIVE; // Actively being manipulated by program ("Use conveyor system" property will be disabled on prod. blocks)
public bool CLEAN; // Actively clear overburdened assembler inputs, or un-scheduled refinery inputs (not used for cargos)
public _Inventory(IMyTerminalBlock invBlock, _BlockType blockType, _Filter[] filters, bool active = true)
{
InvBlock = invBlock;
BlockType = blockType;
Filters = filters;
FILL = false;
EMPTY = false;
ACTIVE = active;
CLEAN = true;
}
public void Reset()
{
foreach (_Filter nextFilter in Filters)
{
nextFilter.Target = 0;
nextFilter.IN_BOUND = true;
nextFilter.OUT_BOUND = true;
}
}
}
public class _Filter
{
public string ItemType;
public string ItemSubType;
public MyFixedPoint Target;
public int Priority;
public bool IN_BOUND;
public bool OUT_BOUND;
public _Filter(string itemType = "null")
{
ItemType = itemType.Split(':')[0];
ItemSubType = itemType.Split(':')[1];
Target = 0; // 0 means no target value, any amount aloud
Priority = 0; // Priority for in-bound requests (WIP)
IN_BOUND = true; // Default to whitelist items in & out of inventory
OUT_BOUND = true;
}
}
public void InventoryListUpdate()
{
Inventories.RemoveAll(x => x.InvBlock == null);
Inventories.RemoveAll(x => !x.InvBlock.CustomName.Contains(Signature));
foreach (IMyCargoContainer nextCargo in Cargos)
{
if (Inventories.FindIndex(x => x.InvBlock == nextCargo) < 0 && nextCargo.CustomName.Contains(Signature))
Inventories.Add(new _Inventory((IMyTerminalBlock)nextCargo, _BlockType.CARGO, FullLibrary));
}
foreach (IMyRefinery nextRefinery in Refineries)
{
if (Inventories.FindIndex(x => x.InvBlock == nextRefinery) < 0 && nextRefinery.CustomName.Contains(Signature))
Inventories.Add(new _Inventory((IMyTerminalBlock)nextRefinery, _BlockType.ASSEMBLER, RefineryLibrary));
}
foreach (IMyAssembler nextAssembler in Assemblers)
{
if (Inventories.FindIndex(x => x.InvBlock == nextAssembler) < 0 && nextAssembler.CustomName.Contains(Signature))
Inventories.Add(new _Inventory((IMyTerminalBlock)nextAssembler, _BlockType.ASSEMBLER, AssembleLibrary, false));
}
foreach (_Inventory nextInventory in Inventories)
{
if (bInventoryRunning && nextInventory.ACTIVE)
InventoryUpdate(nextInventory);
else
InventorySetup(nextInventory);
}
}
So when I started up the code in space engineers today, it suddenly started to work as intended... I have absolutely no idea how or why. I'm leaning towards a memory leak issue related to how the game handles its in-game scripts, however there is the slight possibility that cleaning the code up somehow "massaged" it to do as intended. As I stated before, it was behaving as expected to some degree, it just had un-expected behaviour as the higher collection grew in size. Since each _Inventory was being worked on individually, there should have been no way for its members to get transposed onto another, unless some weird GC-voodoo was happening in the background. As comforting as it is to have my script running properly, it's an even worse feeling knowing I may never find out why it was messing up in the first place, as I have no real solid grounding from which to avoid this problem in the future. Thankyou all for your input.
I know it's been awhile since I posted this, but I feel like I have a responsibility to bring further conclusions to the issue I was having. I only just realized that the problem revolved around a particular class array that I had added to the container class. The _Filter[] within _Inventory, had been passed by reference, rather than being "cloned", something I have just recently become aware of, so every time each individual _Inventory object was being iterated through, their contained arrays were all pointing to the same thing. I have managed to find a way to properly clone each element from the original libraries as needed.

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

XamlBindingHelper Class

Can somebody provide an overview for use of the XamlBindingHelper class with examples? Specifically the GetDataTemplateComponent and SetDataTemplateComponent method.
In the official document, it says
This class is for use in code that is generated by the XAML compiler.
This tells me that I should be able to find some reference of it in code-generated classes (.g.cs) by x:Bind, given there's not a single thread on the Internet that explains what exactly it does.
So I created a test UWP project with a ListView, and inside its ItemTemplate I threw in some x:Bind with x:Phase. After I compiled the project, I found some of its methods used inside my MainPage.g.cs -
XamlBindingHelper.ConvertValue
public static void Set_Windows_UI_Xaml_Controls_ItemsControl_ItemsSource(global::Windows.UI.Xaml.Controls.ItemsControl obj, global::System.Object value, string targetNullValue)
{
if (value == null && targetNullValue != null)
{
value = (global::System.Object) global::Windows.UI.Xaml.Markup.XamlBindingHelper.ConvertValue(typeof(global::System.Object), targetNullValue);
}
obj.ItemsSource = value;
}
Apparently the XamlBindingHelper.ConvertValue method is for converting values. I knew this already, as I used it in one of my recent answers on SO.
XamlBindingHelper.SuspendRendering & XamlBindingHelper.ResumeRendering
public int ProcessBindings(global::Windows.UI.Xaml.Controls.ContainerContentChangingEventArgs args)
{
int nextPhase = -1;
switch(args.Phase)
{
case 0:
nextPhase = 1;
this.SetDataRoot(args.Item);
if (!removedDataContextHandler)
{
removedDataContextHandler = true;
((global::Windows.UI.Xaml.Controls.StackPanel)args.ItemContainer.ContentTemplateRoot).DataContextChanged -= this.DataContextChangedHandler;
}
this.initialized = true;
break;
case 1:
global::Windows.UI.Xaml.Markup.XamlBindingHelper.ResumeRendering(this.obj4);
nextPhase = -1;
break;
}
this.Update_((global::System.String) args.Item, 1 << (int)args.Phase);
return nextPhase;
}
public void ResetTemplate()
{
this.bindingsTracking.ReleaseAllListeners();
global::Windows.UI.Xaml.Markup.XamlBindingHelper.SuspendRendering(this.obj4);
}
XamlBindingHelper.SuspendRendering & XamlBindingHelper.ResumeRendering look very interesting. They seem to be the key functions to enable ListView/GridView's incremental item rendering which helps improve the overall panning/scrolling experience.
So apart from x:DeferLoadingStrategy and x:Load(Creators Update), they are something else that could be used to improve your app performance.
IDataTemplateComponent & IDataTemplateExtension
However, I couldn't find anything related to GetDataTemplateComponent and SetDataTemplateComponent. I even tried to manually set this attached property in XAML but the get method always returned null.
And here's the interesting bit. I later found this piece of code in the generated class.
case 2: // MainPage.xaml line 13
{
global::Windows.UI.Xaml.Controls.Grid element2 = (global::Windows.UI.Xaml.Controls.Grid)target;
MainPage_obj2_Bindings bindings = new MainPage_obj2_Bindings();
returnValue = bindings;
bindings.SetDataRoot(element2.DataContext);
element2.DataContextChanged += bindings.DataContextChangedHandler;
global::Windows.UI.Xaml.DataTemplate.SetExtensionInstance(element2, bindings);
}
break;
The method DataTemplate.SetExtensionInstance looks very similar to XamlBindingHelper.SetDataTemplateComponent. It takes element2 which is the root Grid inside the ItemTemplate of my ListView, and an IDataTemplateExtension; where the latter takes an element and an IDataTemplateComponent. If you have a look at their definitions, their functionalities are very similar, which makes me think if DataTemplate.SetExtensionInstance is the replacement of XamlBindingHelper.SetDataTemplateComponent? I'd love to know if otherwise.
Unlike IDataTemplateComponent, you can get an instance of the IDataTemplateExtension in your code -
var firstItemContainer = (ListViewItem)MyListView.ContainerFromIndex(0);
var rootGrid = (Grid)firstItemContainer?.ContentTemplateRoot;
var dataTemplateEx = DataTemplate.GetExtensionInstance(rootGrid);
In my case, the dataTemplateEx is an instance of another generated class called MainPage_obj2_Bindings, where you have access to methods like ResetTemplate and ProcessBindings.
I assume they could be helpful if you were to build your own custom list controls, but other than that I just can't see why you would ever need them.

WPF Data binding and inventory ObservableCollection treatment

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...
}

Categories