Flex DropDown selection not working when I search for item - arrays

so here is the problem that I have so far. I tried to simplify my code so I can attempt to figure this out, but I have had absolutely no luck. I have a viewstack that contains 1 dropdown per stack. They share the same data provider. What I want to do is to select the item contents from the first one. Once I do that, when I click a button to the next stack I have a function that searches from index 0 to the dataprovider length and if the item from the first stack matches the second one, I want to have the second dropdown pick that item up and display it. I have it matching, and I try to select it, but when I run the application it shows up like nothing is selected. Here is what I have:
edit: I got it to work for a simple example, but when I attempt to use it in my more complicated example, on that button click it for some reason resets the value of selectedIndex to -1. How do I prevent this from happening? This is working code for the simple example
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600" applicationComplete="popList()">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.controls.Alert;
[Bindable]
private var myList : ArrayCollection;
[Bindable]
private var selectedItem : String;
[Bindable]
private var index : int;
[Bindable]
private var ind : int;
private function popList() : void {
myList = new ArrayCollection();
stack.initialize();
myList.addItem("1");
myList.addItem("2");
myList.addItem("3");
myList.addItem("4");
myList.addItem("5");
myList.addItem("6");
first.initialize();
second.initialize();
}
private function goNext() : void {
selectedItem = first.selectedItem;
stack.selectedChild = stackb;
for(index = 0; index < myList.length; index++){
var itemNow : String = myList[index].toString();
if(selectedItem == myList[index].toString()){
ind = index;
}
}
}
]]>
</fx:Script>
<mx:ViewStack id="stack" width="862" height="500">
<s:NavigatorContent id="stacka">
<s:DropDownList x="317" y="174" id="first" dataProvider="{myList}"></s:DropDownList>
<s:Button id="next" x="335" y="263" label="Next" click="goNext()"/>
</s:NavigatorContent>
<s:NavigatorContent id="stackb">
<s:DropDownList x="317" y="174" id="second" dataProvider="{myList}" selectedIndex="{ind}"></s:DropDownList>
</s:NavigatorContent>
</mx:ViewStack>
</s:Application>

I didn't try to run the code, but I have a bunch of observations:
First, don't use the same dataProvider for two separate DropDownLists. It causes weird issues. It makes no sense, I have no idea why; but it does. You could dupe the dataProvider by creating a second collection using the same source. Something like this:
-
second.dataProvider = new ArrayCollection(myList.source);
Second, you shouldn't have to manually call the initialize method on either of the DropDownLists. That is highly unusual. The initialize event is fired as part of the creation process; and I assume the initialization method is part of the default event handler. But, firing that event is not the same as having the component go through it's Lifecycle process.
A ViewStack doesn't initialize it's children before the view changes. So, you are probably setting the selectedIndex on the second DropDownList before that DropDownList is initialized, possibly allowing that drop down list to get lost. You can combat this by setting the creationPolicy to all on the ViewStack.
You can probably solve this issue with binding. Something like this.
-
<s:DropDownList x="317" y="174" id="second" dataProvider="{myList}" selectedIndex="{first.selectedIndex}"></s:DropDownList>
Your code to select the next item is comparing an object to a string, so you'll hever the selectedItem.
You could change your loop to something like this:
for(index = 0; index < myList.length; index++){
if(selectedItem == myList[index]){
second.selectedIndex = index;
}
}
But, if you're using the same dataProvider, or a copy of it, why do you even need the loop? Just use the selectedIndex property:
-
second.selectedIndex = first.selectedIndex
Does that help?
Note: StackOverflow makes it real hard to do code formatting inside a list; sorry for not keeping the numbers in my list.

Related

Using a variable to name an Event Handler

Updated 10 Nov 2022
I have the following code in a Winforms program:
void CreateCheckBoxes(Control parentControl, int left, int top, int lineSpace)
{
List<string> listVariables = new List<string>() { "AllowColumnReorder", "CaptureFocusClick", "ColScaleMode", "ColumnTracking", "RowTracking", "EnsureVisible", "FullRowSelect", "GridLines", "HideSelection", "HoverSelection", "IsFocused", "LabelEdit", "MultiSelect", "Scrollable", "VisualStyles" };
foreach (string varName in listVariables)
{
CheckBox ctlTemp = new CheckBox { Name = "chk" + varName, Text = varName, Top = top, Left = left };
parentControl.Controls.Add(ctlTemp);
top += lineSpace;
}
chkAllowColumnReorder.CheckedChanged += new EventHandler(chkAllowColumnReorder_CheckedChanged);
// The name 'chkAllowColumnReorder' does not exist in the current context
}
It works to the extent that I can create as many CheckBoxes as I like based on the length of listVariables. However, I want the name of the CheckedChanged event handler to be based on the name of the control.
As well as my original question I now find I cannot refer to the CheckBox by the name provided in { Name = "chk" + varName, in the debugger a watch on "Name" returns the name of the form. I have not used this form of constructor before and am struggling to find any documentation on it. Can anybody help on this before I try to move on again please?
Is there a way to do this?
The code itself is produced by a small program where I just past in the names of variables from the main program and it produces the above, and all the vent handlers, which is an enormous time saver.
jimi - I've posted this as an answer as it's too long for a comment.
I ended up with:
Dictionary<string, Action<CheckBox>> m_Actions = new Dictionary<string, Action<CheckBox>>();
then loop through:
m_Actions.Add("chkAllowColumnReorder", (c) => containerListView1.AllowColumnReorder = c.Checked); etc.
and then:
void chkTemp_CheckedChanged(object sender, EventArgs e)
{
CheckBox chkTemp = ((CheckBox)sender);
m_Actions[chkTemp.Name](chkTemp);
}
This is completely new to me and I'm still not sure how (or why) it works but thank you very much for telling me about it - I can now paste variables in to my app's form and produce all the code to add controls to the app I need to test.
I did it because my heart sank at the thought of adding heaps of CheckBoxes to an app to test an ExtendeListView, in the end writing this to produce the code was much more interesting than the testing!

Why does this FilterEventHandler seem to remain in the CollectionViewSource filter even after the filter is set to null?

Summary:
I'm creating a simple application for practice, where the user can maintain and query a collection of Things. On the UI are several TextBoxes and ComboBoxes with which they can filter the collection.
The three buttons I'm concerned with are [Filter], [Random], and [All]. [Filter] applies the current filter options. [Random] applies the current filter options (if any), and then only shows one random entry from the filtered results. [All], as expected, shows the unfiltered collection.
To fully understand the background for the question, I'll provide the relevant code.
Here is where anything having to do with the CollectionViewSource (or any relevant code I'm posting) gets declared:
//Members
private ObservableCollection<Thing> _myDataCollection;
private CollectionViewSource _CVS;
private Thing _randomThing;
//Properties
public ObservableCollection<Thing> MyDataCollection
{
get { return _myDataCollection; }
set
{
if (_myDataCollection!= value)
{
_myDataCollection= value;
RaisePropertyChanged(() => MyDataCollection);
}
}
}
public CollectionViewSource CVS
{
get { return _CVS; }
set
{
if (_CVS != value)
{
_CVS = value;
RaisePropertyChanged(() => CVS);
}
}
}
public ICollectionView CVSView
{
get { return CVS.View; }
}
Here is where the CVS is initialized (in the window view-model's constructor). For now, the data collection is populated with a ton of random things (that's all that RandomizeData() does).
MyDataCollection = new ObservableCollection<Thing>();
RandomizeData();
CVS = new CollectionViewSource();
CVS.Source = MyDataCollection;
Here is the code for the [Filter] button's command:
//Clear any stale filter options and rebuild with the most current ones.
CVSView.Filter = null;
//IF THE FOLLOWING LINE IS UNCOMMENTED, THE ISSUE OCCURS.
//CVS.Filter -= new FilterEventHandler(SingleRandomFromCollectionFilter);
BuildCVSFilter();
The code for the [All] button literally just clears the filter:
CVSView.Filter = null;
The code for the [Random] button. I suspect the issue is coming from the handler added here.
//Clear any stale filter options and rebuild with the most current ones.
CVSView.Filter = null;
//IF THE FOLLOWING LINE IS UNCOMMENTED, THE ISSUE OCCURS.
//CVS.Filter -= new FilterEventHandler(SingleRandomFromCollectionFilter);
BuildCVSFilter();
//Only proceed if there are actually results in the filtered collection.
int resultsCount = CVSView.Cast<Thing>().Count();
if (resultsCount > 0)
{
//Point to a random thing in the filtered collection.
CVSView.MoveCurrentToPosition(random.Next(0, resultsCount));
_randomThing = CVSView.CurrentItem as Thing;
//Add another filter event that further constrains the collection to only contain the random thing.
CVS.Filter += new FilterEventHandler(SingleRandomFromCollectionFilter);
}
And here is the code for that BuildCVSFilter() I've been calling. I use this so that I can use multiple filters concurrently. The "FilterOption" strings are properties that are bound to the values of the UI controls.
if (!string.IsNullOrEmpty(FilterOption1))
{
CVS.Filter += new FilterEventHandler(Fitler1);
}
if (!string.IsNullOrEmpty(FilterOption2) && FilterOption2 != "ignore")
{
CVS.Filter += new FilterEventHandler(Fitler2);
}
if (!string.IsNullOrEmpty(FilterOption3))
{
CVS.Filter += new FilterEventHandler(Filter3);
}
Each filter that gets added this way only sets e.Accepted to false, if applicable, and leaves it alone if it's true.
Issue:
If I click on [Random] at all, it seems like the FilterEventHandler that gets added there does not go away. This makes it so that selecting [Filter] after [Random] won't work as expected, because it's only going to filter from the current collection of one thing. Additionally, selecting [Random] a second time seems to reuse the same random thing (instead of finding a new one). Interestingly enough, clicking [All] still works just fine. It shows everything.
When I go into those [Filter] and [Random] OnCommand methods and explicitly add a line to remove that SingleRandomFromCollectionFilter handler, everything works as expected.
Why would NameEntriesView.Filter = null; work to clear the filter on [All], but not on [Filter] or [Random]? Is there something about the CollectionViewSource and its implementation that I'm not fully understanding?

How can i use Apache Wicket to process multiple model instances on a single form?

I am using Wicket to generate a form to allow users to set values for several model objects. Each model object has two fields, operNam and slaNam. The list of operNam values is taken from a parameter string operationNames and I split this space-separated list as shown below and put each one into an ArrayList called myListResult.
String result = parameters.getString("operationNames");
//split list
ArrayList<String> myListResult = new ArrayList<String>(Arrays.asList(result.split(" ")));
for (String word : myListResult) {
System.out.println("===>" + word);
}
What I want to do, is create a form for the user with two elements for each entry in myListResult. I am adding a TextField containing the value of each operation name in myListResult and an empty TextField to allow the user to enter values for slaNam. The code I'm currently using to do this is below:
for(String operation : myListResult) {
form.add(new TextField<String>("operNam" , new PropertyModel<String>(operation, "operNam")));
form.add(slaNam);
}
add(form);
The problem I have is that the above code is adding TextFields with an id value of operNam for each item in myListResult and this is throwing an error due to duplicate id elements in the html. I also tried doing it as follows:
for(String operation : myListResult) {
form.add(new TextField<String>(operation , new PropertyModel<String>(operation, "operNam")));
form.add(slaNam);
}
add(form);
This avoids the problem of duplicate id elements but causes a problem because now my form elements don't map to my model object which expects a field called operNam and another called slaNam.
My Question is
How would I create form elements that represent an arbitrary number of models without using duplicate element id names.
Heres a image of how I would want the html to look if I had 4 items in myListResult:
You should use a Repeater to add your TextFields. For example a ListView
ListView listview = new ListView("listview", list) {
protected void populateItem(ListItem item) {
item.add(new TextField("textField", item.getModel()));
}
};
With approriate HTML:
<span wicket:id="listview">
<input wicket:id="textField" type="text"></input><br/>
</span>
To use multiple fields in each row, the easiest solution is to use objects that contain these fields:
public class MyRow
{
public String slaNam;
public String operName;
}
And create a List of them:
List<MyRow> myRows = new ArrayList<>();
And use this list in the ListView
ListView<MyRow> listview = new ListView("listview", myRows)
You can use this in the populateItem
protected void populateItem(ListItem<MyRow> item) {
item.add(new TextField("slaNam", new ProperyModel(item, "slaNam")));
item.add(new TextField("operName", new ProperyModel(item, "operNam")));
}
Or, use a ProperyListView for even less code :)

Can I use a DrawItem event handler with a CheckedListBox?

I would like to override the text displayed when an item is added to a checked list box. Right now it is using obj.ToString(), but I want to append some text, without changing the objects ToString method. I have seen examples of handling the DrawItem event for ListBoxs, but when I try to implement them, my event handler is not called. I have noted that the Winforms designer does not seem to allow me to assign a handler for the DrawItem event. Being stubborn, I just added the code myself
listbox1.DrawMode = DrawMode.OwnerDrawVariable;
listbox1.DrawItem += listbox1_DrawItem;
Am I trying to do the impossible?
Not impossible, but incredibly difficult. What you suggest will not work, note the meta-data in class CheckedListBox for method DrawItem:
// Summary:
// Occurs when a visual aspect of an owner-drawn System.Windows.Forms.CheckedListBox
// changes. This event is not relevant to this class.
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public event DrawItemEventHandler DrawItem;
Therefore, your only option is to derive your own class from CheckedListBox, and in my limited testing, this will be a long road. You can handle the drawing simply enough, as such:
public class CustomCheckedListBox : CheckedListBox
{
protected override void OnDrawItem(DrawItemEventArgs e)
{
String s = Items[e.Index].ToString();
s += "APPEND"; //do what you like to the text
CheckBoxState state = GetCheckBoxState(e.State); // <---problem
Size glyphSize = CheckBoxRenderer.GetGlyphSize(e.Graphics, state);
CheckBoxRenderer.DrawCheckBox(
e.Graphics,
e.Bounds.Location,
new Rectangle(
new Point(e.Bounds.X + glyphSize.Width, e.Bounds.Y),
new Size(e.Bounds.Width - glyphSize.Width, e.Bounds.Height)),
s,
this.Font,
false,
state);
}
}
Note the method GetCheckBoxState(). What you get in the DrawItemEventArgs is a DrawItemState, not the CheckBoxState you need, so you have to translate, and that's where things started to go downhill for me.
Soldier on, if you like, this should get you started. But I think it'll be a messy, long road.
I continued the work in DonBoitnotts answer.
The "GetCheckBoxState" is implemented using a very limited set with only two states.
var state = GetItemChecked(e.Index) ? CheckBoxState.CheckedNormal : CheckBoxState.UncheckedNormal;
Vertically aligned the checkbox and left aligned the text.
var b = e.Bounds;
int checkPad = (b.Height - glyphSize.Height) / 2;
CheckBoxRenderer.DrawCheckBox(g, new Point(b.X + checkPad, b.Y + checkPad),
new Rectangle(
new Point(b.X + b.Height, b.Y),
new Size(b.Width - b.Height, b.Height)),
text, this.Font, TextFormatFlags.Left, false, state);

How can I get items from current page in PagedCollectionView?

I've got my objects in PagedCollectionView bound to DataGrid and DataPager.
var pcView = new PagedCollectionView(ObservableCollection<Message>(messages));
How can I easily get items from current page in PagedCollectionView from my ViewModel? I wish there were something like this:
var messagesFromCurrentPage = pcView.CurrentPageItems; // error: no such a property
There are properties like SourceCollection, PageIndex and Count but I don't find them useful in this case. What am I missing here?
If you want to get select items you can just use Linq to do it.
var items = pcView.Where(i => i.SomeCondition == true);
Make sure you add a using statement for System.Linq.
Edit: Whenever I have a question as to what is really going on I just look at the code using Reflector (or ILSpy). In this case here is the relevant code inside GetEnumerator() which is how the Select or Where gets the items in the list:
List<object> list = new List<object>();
if (this.PageIndex < 0)
{
return list.GetEnumerator();
}
for (int i = this._pageSize * this.PageIndex; i < Math.Min(this._pageSize * (this.PageIndex + 1), this.InternalList.Count); i++)
{
list.Add(this.InternalList[i]);
}
return new NewItemAwareEnumerator(this, list.GetEnumerator(), this.CurrentAddItem);
So you can see how it is returning only the items in the current page from this code.

Resources