C# - Multiple selection of items in an Excel's Slicer - c#

Given a list of SlicerItems in a Slicer on a Pivot Table of Excel, using Microsoft.Office.Interop. In Excel, I have to select all the items starting with a specified string.
My code till now is:
public static void SelectItemsStartingWith(string slicerName, string prefix)
{
Slicer slicer = Pivot.Slicers[slicerName];
Book.Application.EnableEvents = false;
IEnumerable<SlicerItem> slicers = slicer.SlicerCache.SlicerItems.Cast<SlicerItem>();
foreach (SlicerItem item in slicers)
{
if (item.Value.StartsWith(prefix))
{
item.Selected = true;
}
else if (item.Selected == true)
{
item.Selected = false;
}
}
}
The problem with my code is that anytime I change the item.Selected property of each item it updates every time the pivot, instead of doing it preferably at the end of the foreach, this causes the app to take hours to cycle a list of few tens of items.
Is there any smarter and more efficient way to do so?

Related

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.

UWP - Listbox scrolls to top after navigating within a frame on the same page

OK, tricky one to explain, but its driving me crazy... Its a UWP App on Windows 10:
I have a main page of which a large part is a Frame (where the main content pages are displayed) on the right is a user control which has a list box in it - when selecting an item in the list it loads the main content page into the frame using a user control event which then calls the Navigate method on the frame - all works fine, except... if you have scrolled down the list then click on an item, the page loads but the listbox scrolls to the top of list - really frustrating!! I can't see why it does this or understand what is going on - can anyone shed some light please?
I know its not reloading the contents and the selecteditem remains selected and does not change.
I'm not familiar with Unity, but after some research in your project, I think that each time you select one Item, you reload all your items in ListBox. For example you can take a look at your UserControl named "PersonPicker":
private void cbCategory_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (isLoaded)
people.AddFilterAndOrder("Person Category," + ((ViewModel.SystemConfiguration.SystemData.WorkCategory)cbCategory.SelectedItem).PluralTitle, loadModel: true);
}
Then I found your AddFilterAndOrder method in BaseListVM:
public void AddFilterAndOrder(string filter = "", string order = "", bool loadModel = false)
{
if (filter != "")
{
string[] items = filter.Split(';');
foreach (string i in items)
{
string[] pair = i.Split(',');
if (pair[1] == "")
filters.Remove(pair[0]);
else
if (filters.Keys.Contains(pair[0]))
filters[pair[0]] = pair[1];
else
filters.Add(pair[0], pair[1]);
}
}
if (order != "")
{
string[] items = order.Split(';');
foreach (string i in items)
{
string[] pair = i.Split(',');
if (pair[1] == "")
orders.Remove(pair[0]);
else
if (orders.Keys.Contains(pair[0]))
orders[pair[0]] = pair[1];
else
orders.Add(pair[0], pair[1]);
}
}
if (loadModel) LoadModel();
}
Since you passed "loadModel" as true to this method, LoadModel() method will be executed, I won't paste your LoadModel() method here again, but in your LoadModel method, you clear the Items and reload Items again. This is why I said you probably have refreshed your list.
So, maybe you can try:
people.AddFilterAndOrder("Person Category," + ((ViewModel.SystemConfiguration.SystemData.WorkCategory)cbCategory.SelectedItem).PluralTitle, loadModel: false);
when one Item is selected.

WinForms (C#) input field with database-driven autocomplete

I am trying to create a text input field with autocomplete functionality. The list of available options is huge (50,000+) and will need to be queried on TextChanged (after the first 3 characters have been entered).
I have a 99%-working solution with TextBox, setting AutoCompleteCustomSource to my new AutoCompleteStringCollection in the TextChanged event, but that results in occasional memory access violations due to a well-documented bug in the underlying AutoComplete implementation...
Microsoft Support say "Do not modify the AutoComplete candidate list dynamically during key events"...
Several SO threads: 1, 2, 3
These threads have some suggestions on how to prevent the exceptions but nothing seems to completely eliminate them, so I'm looking for an alternative. have tried switching to a ComboBox-based solution but can't get it to behave as I want.
After the user types the third character, I update the ComboBox's DataSource but the first item is automatically selected. The user is not able to continue typing the rest of the name.
The ComboBox items are not visible until the user clicks the triangle to expand the list
If the user selects the text they have entered and starts typing, I set DataSource to null to remove the list of suggestions. Doing this puts the cursor at the start of the text, so their characters appear in completely the wrong order!
My View:
public event EventHandler SearchTextChanged;
public event EventHandler InstrumentSelected;
public Instrument CurrentInstrument
{
get { return comboBoxQuickSearch.SelectedItem as Instrument; }
}
public IEnumerable<Instrument> Suggestions
{
get { return comboBoxQuickSearch.DataSource as IEnumerable<Instrument>; }
set
{
comboBoxQuickSearch.DataSource = value;
comboBoxQuickSearch.DisplayMember = "Name";
}
}
public string SearchText
{
get { return comboBoxQuickSearch.Text; }
}
private void comboBoxQuickSearch_TextChanged(object sender, EventArgs e)
{
if (SearchTextChanged != null)
{
SearchTextChanged(sender, e);
}
}
private void comboBoxQuickSearch_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter && InstrumentSelected != null)
{
InstrumentSelected(sender, e);
}
}
My Presenter:
private void SearchTextChanged(object sender, EventArgs e)
{
lock (searchLock)
{
// Do not update list of suggestions if:
// 1) an instrument has already been selected
// (the user may be scrolling through suggestion list)
// 2) a search has taken place within the last MIN_SEARCH_INTERVAL
if (DateTime.Now - quickSearchTimeStamp < minimumSearchInterval
|| (view.Suggestions != null && view.Suggestions.Any(i => i.Name == view.SearchText)))
{
return;
}
string searchText = view.SearchText.Trim();
if (searchText.Length < SEARCH_PREFIX_LENGTH)
{
// Do not show suggestions
view.Suggestions = null;
searchAgain = false;
}
// If the prefix has been entered or changed,
// or another search is needed to display the full sublist
else if (searchText.Length == SEARCH_PREFIX_LENGTH
|| searchText.Substring(0, SEARCH_PREFIX_LENGTH) != searchTextPrefix
|| searchAgain)
{
// Record the current time and prefix
quickSearchTimeStamp = DateTime.Now;
searchTextPrefix = searchText.Substring(0, SEARCH_PREFIX_LENGTH);
// Query matches from DB
IList<Instrument> matches = QueryMatches(searchText);
// Update suggestions
view.Suggestions = matches;
// If a large number of results was received, search again on the next chararacter
// This ensures the full match list is presented
searchAgain = matches.Count() > MAX_RESULTS;
}
}
}
(The searchAgain bit is left over from the TextBox implementation, where the AutoCompleteCustomSource wouldn't always show the complete list if it contained too many items.)
Can I get the ComboBox to work as I want it to, providing suggestions as the user types, given my requirement to query those suggestions on TextChanged?
Is there some other combination of controls I should use for a better user experience, e.g. ListBox?

C# Search datagridview for duplicates

Using ms visual studio and csharp .net4.
This is the code i have to check for duplicates
public void CheckForDuplicate()
{
DataGridViewRowCollection coll = ParetoGrid.Rows;
DataGridViewRowCollection colls = ParetoGrid.Rows;
List<string> listParts = new List<string>();
int count = 0;
foreach (DataGridViewRow item in coll)//379
{
foreach (DataGridViewRow items in colls)//143641
{
if (items.Cells[5].Value == item.Cells[5].Value)
{
if (items.Cells[2].Value != item.Cells[2].Value)
{
listParts.Add(items.Cells["Keycode"].Value.ToString());
count++;
dupi = true;
//txtDupe.Text = items.Cells["Keycode"].Value.ToString();
//this.Refresh();
}
}
}
}
MyErrorGrid.DataSource = listParts;
}
This is the check before it allows the user to save.
private void butSave_Click(object sender, EventArgs e)
{
CheckForDuplicate();
if (dupi == true)
{
txtDupe.Clear();
dupi = false;
}
else
{
SaveMyWorkI();
dupi = false;
}
}
This is the data that it is looking at:
Now, I know the logic must be flawed since it saving regardless.
I'm basically searching through each cell on pareto1 to see if the user has made any duplicates, if so it will not save and instead displays the part number etc in another datagridview....well that's the plan.
So could someone look through this and tell me
1) Where in my logic is this failing, also what about if the checks are correct?
2) Will the list work adding the info, if so is a simple bind to a datagrid view enough to display the results?
3) If this is just a really bad way of searching through could someone provide code that reflects what I am Trying to achieve.
Many thanks for your future comments.
UPDATE:: Ok thanks for the help, my algorithm now works, but my very last problem is displaying the part number that is duplicated on the pareto column, instead it displays the length.
public void CheckForDuplicate()
{
DataGridViewRowCollection coll = ParetoGrid.Rows;
DataGridViewRowCollection colls = ParetoGrid.Rows;
List<string> listParts = new List<string>();
int count = 0;
foreach (DataGridViewRow item in coll)//379
{
foreach (DataGridViewRow items in colls)//143641
{
count++;
if ((items.Cells[5].Value != null))
{
if ((items.Cells[5].Value != null) && (items.Cells[5].Value.Equals(item.Cells[5].Value)))
{
if ((items.Cells[2].Value != null) && !(items.Cells[2].Value.Equals(item.Cells[2].Value)))
{
listParts.Add(items.Cells["Keycode"].Value.ToString());
dupi = true;
}
}
}
}
}
MyErrorGrid.DataSource = listParts;
var message = string.Join(Environment.NewLine, listParts);
//MyErrorGrid.DataSource = message;
MessageBox.Show(message);
}
Even though the message box correctly displays the results? is it something im missing out when binding to my datagrid?
Here is a simple example showing how to perform validation during dataentry. There are various ways you can customise how the errors appear (including some sort of custom dialog to resolve errors) that might give you a better solution.
public partial class Form1 : Form
{
BindingSource bs;
DataTable dt; public Form1()
{
InitializeComponent();
BindingList<BindingClass> data = new BindingList<BindingClass>
{
new BindingClass{ Name = "one" },
new BindingClass { Name = "two"}
};
dataGridView1.DataSource = data;
dataGridView1.CellValidating += new DataGridViewCellValidatingEventHandler(dataGridView1_CellValidating);
}
void dataGridView1_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if (row.Index != e.RowIndex & !row.IsNewRow)
{
if (row.Cells[0].Value.ToString() == e.FormattedValue.ToString())
{
dataGridView1.Rows[e.RowIndex].ErrorText =
"Duplicate value not allowed";
e.Cancel = true;
return;
}
}
}
dataGridView1.Rows[e.RowIndex].ErrorText = string.Empty;
}
}
public class BindingClass
{
public string Name { get; set; }
}
}
Naturally this won't always fit your requirements of what users like to work with but I thought it could help to see another option.
You are doing comparisons with == and != .
items.Cells[5].Value exposes an object.
In your case this is most likely doing an equality check based on reference equality, which is probably not what your want. Try using something like items.Cells[5].Value.Equals(item.Cells[5].Value)
Please also consider solving such problems on the simplest abstractions available. E.g. if you had the grid bound to a collection of objects, then you could perform the cleanup operation against that collection of objects, disregarding any UI you bolt on top of it.
You can also consider using the Distinct extension method from the LINQ namespace and provide it an IEqualityComparer* to make sure the most efficient code for removing duplicates available in the .NET Framework is used by you.
*) IEqualityComparer is an abstraction that allows you to define in one place when you consider two objects to be equal. Distinct provides an overload where you can specify such a comparer.
See if this can work for you
var dup = dataGridView1.Rows.Cast<DataGridViewRow>().Distinct().Where(g => g.Index != 0);
Excluding row with index 0. It is header row.

how to delete a specific text / message showed in a ListView ?

Hey!
i will try to explain my question better here and my situation :)
i have a ListView in my application, this ListView is used for output errors , so its kinda used for error log.
My program is mainly a timer, that is checking for changes in int values.
when an error occurs I want to show it in my list.
Problem is that the error will then be added like 3-4 times, maybe more.
If i do a check if(exist.in.listbox) i will not be able to add the same error second time. What I want to do is to delete specific message/error and then delete it when its "fixed" or no longer a threat to the system.
here is code snippet:
if (Convert.ToInt32(x) == 1 && Convert.ToInt32(y) == 1)
{
found = false;
ListViewItem item = new ListViewItem(message);
foreach (ListViewItem z in listView1.Items)
{
if (z.Text == message)
{ found = true; }
}
if (found == false)
{
item.SubItems.Add(now.ToString());
listView1.Items.Add(item);
listView1.EnsureVisible(item.Index);
}
}
else
{
foreach (ListViewItem z in listView1.Items)
{
if (z.Text == message)
{
//no longer a threat, delete the message added aboue
}
}
}
cant find anything about this. only "when selected --> delete the line"
can think about this like the error window in visual, when u get a red error, u can fix the code.and it disapair :)
You should follow the same approach as when you add items. First, check if you want to perform the deletion, and then remove the item.
If this does not perform well (iterating through the list twice on each timer iteration), you could also move your "add/remove" logic out of the ListView (ie, maybe keep a Dictionary for faster lookup).
void DeleteIfNecessary(string message)
{
ListViewItem listViewItem = FindListViewItemForMessage(message);
if (listViewItem == null)
{
// item doesn't exist
return;
}
this.listView1.Items.Remove(listViewItem);
}
private ListViewItem FindListViewItemForMessage(string s)
{
foreach (ListViewItem lvi in this.listView1.Items)
{
if (StringComparer.OrdinalIgnoreCase.Compare(lvi.Text, s) == 0)
{
return lvi;
}
}
return null;
}

Categories