Swapping Objects in an Array - C# - c#

In C#, I have an Array of MenuItem. I'm trying to swap the two Objects in index 2 and index 3 of the array without success using the code below:
MenuItem Temp = Items[2];
Items[2] = Items[3];
Items[3] = Temp;
There must be a reason why the second and third lines aren't working in C# which I may not understand yet. Is anyone able to clarify it a bit more? Do I have to go deeper and swap each property in the objects individually?
Edited - Sorry. Looks like I made a mess of the code when trying to clean it up for posting. Corrected now.
The actual code is :
MenuItem TempButton = MenuItems.Items[SelectedButton.CountId];
MenuItems.Items[SelectedButton.CountId] = MenuItems.Items[SelectedButton.CountId + 1];
MenuItems.Items[SelectedButton.CountId + 1] = TempButton;
MenuItems.Items is an array of MenuItem
Looking at the Watch I have placed on MenuItems.Items, nothing happens on Line 2 or 3.
The MenuItems.Items property has get and set functions, which may be causing the issue... Will investigate further...

You are settings Items[2] to Temp, which was Items[2] to begin with, so you are effectively not doing anything. I don't know what SelectedButton.CountId is supposed to be.
But if you just want to swap indices 2 and 3, you can do this:
Item Temp = Items[2];
Items[2] = Items[3];
Items[3] = Temp;

Is SelectedButton.CountId = 2? if so I would try this:
Item Temp = MenuItems.Items[2];
MenuItems.Items[SelectedButton.CountId] = MenuItems.Items[3];
MenuItems.Items[3] = Temp;
Note the last line has a 3 in it.
This would be clearer:
Item Temp = MenuItems.Items[SelectedButton.CountId];
MenuItems.Items[SelectedButton.CountId] = MenuItems.Items[3];
MenuItems.Items[3] = Temp;

I have no idea what SelectedButton.CountId is supposed to be but you're putting Temp right back in the same slot it was to begin with. And MenuItems.Items seems to be a totally different collection from Items.
string[] items = { "one", "two", "three" };
string temp = items[1]; // temp = "two"
items[1] = items[2]; // items[1] = "three"
items[2] = temp; // items[2] = "two"
// items is now
// { "one", "three", "two" }

try:
Item Temp = Items[SelectedButton.CountId];
Items[SelectedButton.CountId] = MenuItems.Items[SelectedButton.CountId+1];
Items[SelectedButton.CountId+1] = Temp;
This should swap in a bubble fashion

I remember running into a similar source of confusion some time ago, with the DataRow.ItemArray property. This property was extremely counterintuitive for the very same reason that the Items property in your example seems so odd.
What was ultimately so confusing was that the property was designed to be copied from and assigned to, just like you normally would with a field of value type (like int, double, etc.). That is, to change the element at index 2, this would not work:
row.ItemArray[2] = "New Value";
The above code would essentially copy the values from the row into a new array, take that copy and set the value at index 2 to "New Value," and then the new array would immediately be out of scope. The way this property was supposed to work was:
object[] items = row.ItemArray;
items[2] = "New Value";
row.ItemArray = items;
Very counterintuitive, in my book (note to library developers: don't do this). But it sounds like this is probably the problem behind the issue you were seeing with your code.
In other words, I think the swapping code you have (now) is correct. The problem lies with whoever had the bright idea of making that Items property behave as if it's a value field.

I had the same problem as I wanted to move elements in a WPF-TreeView up and down. Since none of the answers solved the problem for me here is the best I could find.
private void MoveLayerUp()
{
if(Layers.SelectedItem != null)
{
int index = Layers.Items.IndexOf(Layers.SelectedItem);
if (index > 0)
{
var swap = Layers.Items[index - 1];
Layers.Items.RemoveAt(index - 1);
Layers.Items.Insert(index, swap);
}
}
}
private void MoveLayerDown()
{
if (Layers.SelectedItem != null)
{
int index = Layers.Items.IndexOf(Layers.SelectedItem);
if (index < Layers.Items.Count-1)
{
var swap = Layers.Items[index + 1];
Layers.Items.RemoveAt(index + 1);
Layers.Items.Insert(index, swap);
}
}
}
This solves the problem of assigning elements in the collection. Further it has the advantage that the current seleced item is never touched and stays selected.

Solved the issue. I think I complicated the problem a bit too much.
MenuItems.Items was a property with get/set functions that returns/sets a private ArrayList.
I created a function in the class for MenuItems which swapped the indexes in the private ArrayList. (which used standard swapping code similar to what I tried and what everyone has mentioned in their replies.)
Thanks for everyone's help.

Related

Number increments too much

I've got a DataGrid, which is bound to a List: _bhaList.
Now, I've written a method which will allow the user to select an item on the grid, and move it up. And as it moves up, the No. column is updated to show it's current position. However, in this particular case, where I have 3 items, if I choose No. 2, and move it up, it does switch places with No. 1, and those numbers update, but No. 3 will change to 4. If I repeat the process, the new No. 4 will change to 5.
Here's the code that I've attempted:
var oldIndex = grdBha.SelectedIndex;
var newIndex = oldIndex - 1;
var bha = _bhaList[oldIndex];
_bhaList.RemoveAt(oldIndex);
bha.Number = oldIndex;
_bhaList.Insert(newIndex, bha);
for (var i = newIndex + 1; i <= _bhaList.Count; i++)
{
if (i != _bhaList.Count)
{
_bhaList[i].Number += 1;
}
}
I've tried different variations in the for loop, with no success.
I have the feeling that this is going to be something really simple, but my mind just isn't seeing it.
Your problem is that only two items in this case had their "order changed", yet you incremented the number for everybody below the item moved. In this case Bit X/O Sub was 3, but gets incremented to 4.
You know the only two indices involved:
// swap the two
var temp = _bhaList[newIndex];
_bhaList[newIndex] = _bhaList[oldIndex];
_bhaList[oldIndex] = temp;
_bhaList[newIndex].Number = newIndex + 1;
_bhaList[oldIndex].Number = oldIndex + 1;
If your items can only move one spot at a time the loop is unnecessary and can be replaced with the following:
_bhaList[oldIndex].Number++;
You only need to reshuffle/ swap the locations,
When you _bhaList.Insert(newIndex, bha); it will increment the count by 1.
Try only by replacing those two selected indexes.
Your for loop doesn't make sence, you don't need to increase the number of each items. You only need to do this if you add a new item in the list.
var oldIndex = grdBha.SelectedIndex;
var newIndex = oldIndex - 1;
var oldBha = _bhaList[oldIndex];
var newBha = _bhaList[newIndex];
oldBha.Number--;
newBha.Number++;
_bhaList.RemoveAt(oldIndex);
_bhaList.Insert(newIndex, oldBha);
or
_bhaList[oldIndex] = newBha;
_bhaList[newIndex] = oldBha;
by using
_bhaList.RemoveAt(oldIndex);
bha.Number = oldIndex;
_bhaList.Insert(newIndex, bha);
you have moved the item to the new index. .NET will push down all the other elements as required with out the need for you to do so. As such there is no need for the for loop.
To resolve your issue, simply remove the for loop.

Iterating over an array using index and assigning values depending on index

I was facing this problem earlier today, and since I could not find a satisfactory solution, I decided to change my class design, and have seperate properties such as Tag 1, Tag 2, Tag 3 etc.
My main problem is the fact that I need to bind a grid to an object that contains a list among other properties and I need to show each item in the list as a separate column which I am unable to do. Hence I am resorting to declaring variables separately. Original question is here...
Now, I'm facing one of the most common design problem that probably every programmer has at some point of time. Here is the code to demonstrate it,
for (int i = 0; i < tags.Length; ++i) // Length not known here.
{
if(i==0){
tag1 = tags[0];
}
else if(i == 1){
tag2 = tags[1];
}
else if(i == 2){
tag3 = tags[2];
}
....
}
Here tags is a string array.
I was wondering if there is a more elegant way to do this. Another thing to note is that the efficiency of this loop decreases as it progresses, since with more iterations it has to check more conditions. If we could remove a condition after it had become true once it would speed up each iteration since we know that each condition will become true only once in all the iterations
Moved answer about DataGridView and using ComponentModel to the correct question:
Displaying a list of object containing a list in a grid view
Briefing
The DataGridView controll supports the ComponentModel namespace so that you can create classes that appear to have properties that don't exist. It is the same mechanism the PropertyGrid uses.
The sample code is in this answer of that question:
https://stackoverflow.com/a/13078735/195417
OLD ANSWER
This was my previous answer, when I didn't realize the real question was about the DataGridView control.
Isn't this the same as setting the values directly:
this.tag1 = tags[0];
this.tag2 = tags[1];
this.tag3 = tags[2];
EDIT: as you sayd you don't know how many variables will be needed, then you need only one, and that is a list:
var list = new List<string>();
for (int i = 0; i < tags.Length; ++i)
{
list.add(tags[i]);
}
If all you want is to copy all values, you can even do this:
var list = new List<string>(tags);
Tell me whether this is what you want or not... maybe I have misunderstood the question.
The whole loop is pointless. But unless the tags array length is always going to be the same, you have to be sure not to go out of bounds...
if(tags.Length >= 1) this.tag1 = tags[0];
if(tags.Length >= 2) this.tag2 = tags[1];
if(tags.Length >= 3) this.tag3 = tags[2];
if(tags.Length >= 4) this.tag4 = tags[3];
if(tags.Length >= 5) this.tag5 = tags[4];
... so on for however many this.tag# you have.
This is essentially the same:
for(int index = 0; index < tags.Length[]; index++){
switch(index){
case 0:
tag1 = tags[0];
break;
// And so on
}
}

Insert an underlying value into a non-existing index

I'm trying to solve a simple algorithm a specific way where it takes the current row and adds it to the top most row. I know there are plenty of ways to solve this but currently I have a text file that gets read line by line. Each line is converted to an sbyte (there's a certain reason why I am using sbyte but it's irrelevant to my post and I won't mention it here) and added to a list. From there, the line is reversed and added to another list. Here's the code I have for that first part:
List<List<sbyte>> largeNumbers = new List<List<sbyte>>();
List<string> total = new List<string>();
string bigIntFile = #"C:\Users\Justin\Documents\BigNumbers.txt";
string result;
StreamReader streamReader = new StreamReader(bigIntFile);
while ((result = streamReader.ReadLine()) != null)
{
List<sbyte> largeNumber = new List<sbyte>();
for (int i = 0; i < result.Length; i++)
{
sbyte singleConvertedDigit = Convert.ToSByte(result.Substring(i, 1));
largeNumber.Add(singleConvertedDigit);
}
largeNumber.Reverse();
largeNumbers.Add(largeNumber);
}
From there, I want to use an empty list that stores strings which I will be using later for adding my numbers. However, I want to be able to add numbers to this new list named "total". The numbers I'll be adding to it are not all the same length and because so, I need to check if an index exists at a certain location, if it does I'll be adding the value I'm looking at to the number that resides in that index, if not, I need to create that index and set it's value to 0. In trying to do so, I keep getting an IndexOutOfRange exception (obviously because that index doesn't exist). :
foreach (var largeNumber in largeNumbers)
{
int totalIndex = 0;
foreach (var digit in largeNumber)
{
if (total.Count == 0)
{
total[totalIndex] = digit.ToString(); //Index out of Range exception occurs here
}
else
{
total[totalIndex] = (Convert.ToSByte(total[totalIndex]) + digit).ToString();
}
totalIndex ++;
}
}
I'm just at a loss. Any Ideas on how to check if that index exists; if it does not create it and set it's underlying value equal to 0? This is just a fun exercise for me but I am hitting a brick wall with this lovely index portion. I've tried to use SingleOrDefault as well as ElementAtOrDefault but they don't seem to be working so hot for me. Thanks in advance!
Depending on if your result is have small number of missing elements (i.e. have more than 50% elements missing) consider simply adding 0 to the list till you reach neccessary index. You may use list of nullable items (i.e. List<int?>) instead of regular values (List<int>) if you care if item is missing or not.
Something like (non-compiled...) sample:
// List<long> list; int index; long value
if (index >= list.Count)
{
list.AddRange(Enumerable.Repeat(0, index-list.Count+1);
}
list[index] = value;
If you have significant number of missing elements use Dictionary (or SortedDictionary) with (index, value) pairs.
Dictionary<int, long> items;
if (items.ContainsKey(index))
{
items[key] = value;
}
else
{
items.Add(index, value);
}

Append int to end of string or textbox name in a For Loop C#

I have a C# application in which there are several textboxes with the same name except for a number on the end which starts at 1 and goes to 19. I was hoping to use a for loop to dynamically add values to these text boxes by using an arraylist. There will be situations where there will not be 19 items in the arrayList so some text boxes will be unfilled. Here is my sample code for what I am trying to do. Is this possible to do?
for (int count = 0; count < dogList.Count; count++)
{
regUKCNumTextBox[count+1].Text=(dogList[count].Attributes["id"].Value.ToString());
}
So you've got a collection of text boxes that are to be filled out top-to-bottom? Then yes, a collection of TextBox seems appropriate.
If you stick your TextBox references in an array or a List<TextBox> -- I wouldn't use an ArrayList as it's considered deprecated in favor of List<T> -- then yes, you can do that:
TextBox[] regUKCNumTextBox = new []
{
yourTextBoxA,
yourTextBoxB,
...
};
Then yes your logic is possible, you can also query the control by it's name, though that would be heavier at runtime - so it's a tradeoff. Yes, in this solution you must set up a collection to hold your text box references, but it will be more performant.
Try this:
(By the way I am assuming you use WinForms)
for (int count = 0; count < dogList.Count; count++)
{
object foundTextBox = this.Controls.Find("nameofTextBoxes" + [count+1]);
if (foundTextBox != null)
{
if (foundTextBox is TextBox)
{
((TextBox)foundTextBox).Text=(dogList[count].Attributes["id"].Value.ToString());
}
}
}
With this code you are trying to find a Control form your Forms Controls collection. Then you have to make sure the control is of the TextBox type. When it is; cast it to a TextBox and do what you want with it. In this case; assign a value to the Text property.
It would be more efficient to keep a collection of your TextBoxes like in the solution offered by James Michael Hare
Yikes; something doesn't seem quite right with the overall design there; but looking past that, here's a quick stab at some pseudo code that might work:
for (int count = 0; count < dogList.Count; count++)
{
var stringName = string.Format("myTextBoxName{0}", count);
var ctrl = FindControl(stringName);
if(ctrl == null) continue;
ctrl.Text = dogList[count];
}

DateTimeControl in Sharepoint (c#)

I'm trying to change the default values in the "time" drop down list that the DateTimeControl displays in Sharepoint. I want an increment of 15, not 5 minutes. Anybody has any idea how this could be done? Can I overload a method or something?
As a matter of fact both the time drop down and its initializers are implemented as private data members of the DateTimeControl class so you can not change the values directly. However, the minutes drop down is prepared inside OnPreRender, we can get the control and reset its values indirectly to get desired behavior. Here is one approach
public class MyDateTimeControl : DateTimeControl
{
protected override void Render(HtmlTextWriter output)
{
DropDownList minuteControl = null;
string[] newMinutesRange = new string[] { "00", "15", "30", "45" };
string[] newMinutesRangeExt = new string[] { "00", "15", "30", "45", "" };
int index = 0;
int selectedMinutes;
try
{
if (!this.DateOnly && this.Controls.Count == 4)
{
minuteControl = (DropDownList)this.Controls[2];
}
}
catch { }
if (minuteControl != null && !this.DateOnly)
{
selectedMinutes = Convert.ToInt32(minuteControl.SelectedValue);
if (selectedMinutes % 15 > 0)
{
index = 4;
newMinutesRangeExt.SetValue(selectedMinutes.ToString(), index);
newMinutesRange = newMinutesRangeExt;
}
else
{
index = selectedMinutes / 15;
}
minuteControl.Items.Clear();
minuteControl.SelectedIndex = 0;
minuteControl.DataSource = newMinutesRange;
minuteControl.DataBind();
minuteControl.SelectedIndex = index;
}
base.Render(output);
}
}
Hope this helps
Go to
[12]\TEMPLATE\LAYOUTS\1033\BFORM.JS
In line : 7690
Change:
this.dminControl=5;
to
this.dminControl=15;
It works, but all DateTimePickers will have an increment to 15 minutes
Unfortunately this is not possible using the out of the box DateTime field.
A SharePoint field is made up of 2 main parts. The data structure (in code) and the various views (namely in a list, new/edit/view, admin [when adding to a list]). The data structure out of the box is a standard .NET DateTime field. Unfortunately the views only give the increment by 5 minutes.
You can create your own by inheriting from the default field. MSDN has a decent explaination of how. Nick Sevens has a much clearer explanation.
Unfortunately (as with most SharePoint customizations) creating your own field in CAML can be tricky.
This project on CodePlex might be a good starting point. It's licensed under the GPL so you can modify it.
You can get the value of selected Hour and Minute from the SharePoint:DateTimeControl in C# by the following code:
DateTimeControlName.SelectedDate.Hour
&
DateTimeControlName.SelectedDate.Minute.
These statements will return the hour and minute in Integer format.
I know this post is quite old. But I just thought this may help someone coming to this post.

Categories