I'm currently trying to figure out how to get the previous Selected Item in a Combobox, the data is added in a list in the Form1_Load function.
//Flavour Change Button
private void CoboFlav_SelectionChangeCommitted(object sender, EventArgs e)
{
var selectedItemPrice = (coboFlav.SelectedItem as Flavour).price;
var selectedItemName = (coboFlav.SelectedItem as Flavour).name;
var pre_item = pre_selIndex = (coboFlav.SelectedItem as Flavour).price;
//var previousItem = flavourTea_Previous_Var = (coboFlav.SelectedItem as Flavour).price;
//Item List
Flavour listItem1 = ListCopy.MyList2.Find(x => (x.name == "- None -"));
Flavour listItem2 = ListCopy.MyList2.Find(x => (x.name == "Lemon"));
Flavour listItem3 = ListCopy.MyList2.Find(x => (x.name == "Passionfruit"));
Flavour listItem4 = ListCopy.MyList2.Find(x => (x.name == "Yogurt"));
//Checking Base Tea Box for adding price to currentItemTotal
if (coboFlav.Text == listItem1.name || coboFlav.Text == listItem2.name || coboFlav.Text == listItem3.name || coboFlav.Text == listItem4.name)
{
//Increment Item Cost Value & take away previous item cost
currentTotalItemCost += selectedItemPrice - pre_item;
}
//Update CUrrentTotal Text
CurrentTotal.Text = currentTotalItemCost.ToString();
}
If the user selected an option in the combobox the selectedPrice int is increased. I am trying to take away the previousItemCost and im having trouble understanding how to find the previous selected user input.
I am not really sure how to approach this, I have seen a couple of other people declare a new int as -1 and set the SelectedIndex to that. But I don't really understand that solution. If someone could point me in the right direction that would be awesome. Also I am quite new to windows forms as I came from a Unity background.
Thanks
Apparently you want a special kind of ComboBox. In your "object oriented programming" course you learned that if you want a class similar to another class, but with some special behaviour, you should create a derived class:
class PreviousSelectedComboBox : ComboBox // TODO: invent proper name
{
private int previousSelectedIndex = -1;
[System.ComponentModel.Browsable(false)]
public virtual int PreviousSelectedIndex {get; private set;}
{
get => this.previousSelectedIndex;
set => this.previousSelectedIndex = value;
}
[System.ComponentModel.Browsable(false)]
public override int SelectedIndex
{
get => base.SelectedIndex;
set
{
if (this.SelectedIndex != value)
{
this.PreviousSelectedIndex = this.SelectedIndex;
base.SelectedIndex = value;
// TODO: call OnSelectedIndexChanged?
}
}
}
}
Test In a dummy test program or a unit test, check if ComboBox.OnSelectedIndexChanged is called by base.SelectedIndex, If not, call it in the SelectedIndex.Set.
Also check what happens if ComboBox.SelectedItem.Set is called. Does this change the selected index by calling your overridden property SelectedIndex?
Event: I don't think that you need an event PreviousSelectedIndexChanged. It won't add anything, because this event is raised whenever event SelectedIndexChanged is raised, so those who want to get notified when PreviousSelectedIndex changes, could subsbribe to event SelectedIndexChanged.
Still, if you want such an event, follow the pattern that is used in property SelectedIndex and in OnSelectedIndexChanged.
Related
I have a DataGrid which looks like:
<DataGrid Grid.Row="3" Grid.Column="1" ItemsSource="{Binding Purchases}" SelectionMode="Single" SelectionUnit="FullRow"
SelectedItem="{Binding SelectedPurchase, Source={x:Static ex:ServiceLocator.Instance}}"
AutoGenerateColumns="False" CanUserAddRows="False">
<e:Interaction.Triggers>
<e:EventTrigger EventName="CellEditEnding">
<e:InvokeCommandAction Command="{Binding DataContext.CellEditEndingCommand,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Page}}}"/>
</e:EventTrigger>
</e:Interaction.Triggers>
<DataGrid.Columns>
.......
........
<DataGrid.Columns>
</DataGrid>
Property SelectedPurchase looks like:
private Purchase _selectedPurchase;
public Purchase SelectedPurchase
{
get
{
return _selectedPurchase;
}
set
{
_selectedPurchase = value;
NotifyPropertyChanged("SelectedPurchase");
}
}
CellEditEndingCommand
public ICommand CellEditEndingCommand { get; set; }
private void CellEditEndingMethod(object obj)
{
XDocument xmlPurchases = XDocument.Load(DirectoryPaths.DataDirectory + "Purchases.xml");
var currentPurchaseInData = (from purchase in xmlPurchases.Element("Purchases").Elements("Purchase")
where Convert.ToInt32(purchase.Attribute("Id").Value) == ServiceLocator.Instance.SelectedPurchase.Id
select purchase).FirstOrDefault();
currentPurchaseInData.SetElementValue("CreditorId", ServiceLocator.Instance.SelectedPurchase.Creditor.Id);
currentPurchaseInData.SetElementValue("AnimalId", ServiceLocator.Instance.SelectedPurchase.Animal.Id);
currentPurchaseInData.SetElementValue("QuantityInLitre", ServiceLocator.Instance.SelectedPurchase.Litre);
currentPurchaseInData.SetElementValue("FAT", ServiceLocator.Instance.SelectedPurchase.FAT);
currentPurchaseInData.SetElementValue("RatePerLitre", ServiceLocator.Instance.SelectedPurchase.RatePerLitre);
xmlPurchases.Save(DirectoryPaths.DataDirectory + "Purchases.xml");
}
Now If I change any value in DataGridCell and then I hit Enter CellEditEndingCommand is fired and CellEditEndingMethod is fired. But If I keep a breakpoint inside CellEditEndingMethod and take a look at it, then I can see that Values of any property of SelectedPurchase does not change to new values.
Let me give an example to explain the above line more correctly:
When I keep a breakpoint on any line inside CellEditEndingMethod and take a look at Properties like Litre, FAT etc., these properties values does not change. I mean I expect the property to take new value but it holds old value. Also, In view I can see the new values but in XML file there are still old values.
Update:
Purchases = new ObservableCollection<Purchase>(
from purchase in XDocument.Load(DirectoryPaths.DataDirectory + "Purchases.xml")
.Element("Purchases").Elements("Purchase")
select new Purchase
{
Id = Convert.ToInt32(purchase.Attribute("Id").Value),
Creditor = (
from creditor in XDocument.Load(DirectoryPaths.DataDirectory + "Creditors.xml")
.Element("Creditors").Elements("Creditor")
where creditor.Attribute("Id").Value == purchase.Element("CreditorId").Value
select new Creditor
{
Id = Convert.ToInt32(creditor.Attribute("Id").Value),
NameInEnglish = creditor.Element("NameInEnglish").Value,
NameInGujarati = creditor.Element("NameInGujarati").Value,
Gender = (
from gender in XDocument.Load(DirectoryPaths.DataDirectory + #"Basic\Genders.xml")
.Element("Genders").Elements("Gender")
where gender.Attribute("Id").Value == creditor.Element("GenderId").Value
select new Gender
{
Id = Convert.ToInt32(gender.Attribute("Id").Value),
Type = gender.Element("Type").Value,
ImageData = gender.Element("ImageData").Value
}
).FirstOrDefault(),
IsRegisteredMember = creditor.Element("IsRegisteredMember").Value == "Yes" ? true : false,
Address = creditor.Element("Address").Value,
City = creditor.Element("City").Value,
ContactNo1 = creditor.Element("ContactNo1").Value,
ContactNo2 = creditor.Element("ContactNo2").Value
}
).FirstOrDefault(),
Animal = (
from animal in XDocument.Load(DirectoryPaths.DataDirectory + #"Basic\Animals.xml")
.Element("Animals").Elements("Animal")
where animal.Attribute("Id").Value == purchase.Element("AnimalId").Value
select new Animal
{
Id = Convert.ToInt32(animal.Attribute("Id").Value),
Type = animal.Element("Type").Value,
ImageData = animal.Element("ImageData").Value,
Colour = animal.Element("Colour").Value
}
).FirstOrDefault(),
Litre = Convert.ToDouble(purchase.Element("QuantityInLitre").Value),
FAT = Convert.ToDouble(purchase.Element("FAT").Value),
RatePerLitre = Convert.ToDouble(purchase.Element("RatePerLitre").Value)
}
);
The CellEditEnding Event is not meant to update the datarow but to validate the single cell and keep it in editing mode if the content is not valid. The real update is done when the whole row is committed. Try it by adding the code in the HandleMainDataGridCellEditEnding method in http://codefluff.blogspot.de/2010/05/commiting-bound-cell-changes.html to your CellEditEndingMethod. It is good explained there. You may replace the if (!isManualEditCommit) {} by if (isManualEditCommit) return;.
UPDATE
You can extend your Purchase class by interface IEditableObject. DataGrid will call the method EndEdit() of this interface after the data has been committed and so you can do the XML stuff there. So you don't need any further buttons because a cell goes in edit mode automatically and the commit is done when you leave the row.
I think the CollectionChanged solution does not work because if you edit a dataset all changes take place inside the single object (Purchase) and not in the collection. CollectionChanged will be called by adding or removing an object to the collection
2nd UPDATE
Another try by putting it all together:
I simplified your Purchase class for demonstration:
class Purchase
{
public string FieldA { get; set; }
public string FieldB { get; set; }
}
Create a derived class to keep the real Purchase class clean:
class EditablePurchase : Purchase, IEditableObject
{
public Action<Purchase> Edited { get; set; }
private int numEdits;
public void BeginEdit()
{
numEdits++;
}
public void CancelEdit()
{
numEdits--;
}
public void EndEdit()
{
if (--numEdits == 0)
{
if (Edited != null)
Edited(this);
}
}
}
This is explained in SO WPF DataGrid calls BeginEdit on an IEditableObject two times?
And create the Purchases collection:
ObservableCollection<EditablePurchase> Purchases = new ObservableCollection<EditablePurchase>()
{
new EditablePurchase {FieldA = "Field_A_1", FieldB = "Field_B_1", Edited = UpdateAction},
new EditablePurchase {FieldA = "Field_A_2", FieldB = "Field_B_2", Edited = UpdateAction}
};
Purchases.CollectionChanged += Purchases_CollectionChanged;
private void Purchases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (EditablePurchase item in e.NewItems)
item.Edited = UpdateAction;
}
void UpdateAction(Purchase purchase)
{
// Save XML
}
This provides that the calls to Edited are catched for all EditablePurchase elements from initialization and for newly created ones. Be sure to have the Edited property set in initializer
This is a disgrace for WPF. No DataGrid.CellEditEnded event? Ridiculous, and I didn't know about that so far. It's an interesting question.
As Fratyx mentioned, you can call
dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
in a code behind CellEditEnding method. While it works, I find it's quite ugly. Not only because of having code behind (could use a behavior to circumnavigate that), but your ViewModel CellEditEndingMethod will be called twice, once for no good reason because the edit is not yet committed.
I would probably opt to implement INotifyPropertyChanged in your Purchase class (I recommend using a base class so you can write properties on one line again) if you haven't already, and use the PropertyChanged event instead:
public MyViewModel()
{
Purchases = new ObservableCollection<Purchase>();
Purchases.CollectionChanged += Purchases_CollectionChanged;
}
private void Purchases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (Purchase item in e.NewItems)
item.PropertyChanged += Purchase_PropertyChanged;
if (e.OldItems != null)
foreach (Purchase item in e.OldItems)
item.PropertyChanged -= Purchase_PropertyChanged;
}
private void Purchase_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// save the xml...
}
You will not get any CollectionChanged event before DataGrid is changing the collection. And this does not before happen a dataset is committed.
If you press 'Enter' in a cell you change the value of this cell in a kind of copy of the real dataset. So it is possible to skip the changes by rollback. Only after finishing a row e.g. by changing to another row or direct commit your changed data will be written back to the original data. THEN the bindings will be updated and the collection is changed.
If you want to have an update cell by cell you have to force the commit as in the code I suggested.
But if you want to have a puristic MVVM solution without code behind you have to be content with the behavior DataGrid is intended for. And that is to update after row is finished.
I've been searching for about an hour already and couldn't find a best solution.
I am migrating from VB.NET to C# Forms and to C# WPF.
Never mind that...
so I use this code for C# forms and it works, but not in C# WPF
if (ListView1.SelectedItems.Count > 0)
{
for (lcount = 0; lcount <= ListView1.Items.Count - 1; lcount++)
{
if (ListView1.Items[lcount].Selected == true)
{
var2 = lcount;
break;
}
}
}
this is the way I want to get the index of the item clicked in listbox.
I have the error in .SELECTED
please help.
You can get SelectedIndex from listView. No need to traverse over all items because as per your code you seems to be interested in index of any selected item.
var2 = ListView1.SelectedIndex;
OR
simply this will work if interested in only first index:
if (lst.SelectedItems.Count > 0)
{
var2 = lst.Items.IndexOf(lst.SelectedItems[0]);
}
If you are using the .NET Compact Framework, SelectedIndex is not supported. For a general solution, I prefer SelectedIndices:
ListView.SelectedIndexCollection indices = lst.SelectedIndices;
if (indices.Count > 0)
{
// Do something with indices[0]
}
For Visual Studio 2015, SelectedIndex does not seem to be available. Instead, you can use SelectedIndices[x] where x=0 will give you the first selected item:
listView.SelectedIndices[0]
You can also set the MultipleSelect property to false to only allow one item to be selected at a time.
It can return NULL. Also the SelectedIndexChanged event can be FIRED TWICE. And the first time, there nothing selected yet.
So the only safe way to find it is like this:
private void lv1_SelectedIndexChanged(object sender, EventArgs e)
{
if (lv1.FocusedItem == null) return;
int p = lv1.FocusedItem.Index;
... now int p has the correct value...
sColl.Clear();
string item = String.Empty;
if (listView1.SelectedItems.Count > 0) {
for (int i = 0; i < listView1.SelectedItems.Count; i++) {
if (listView1.SelectedItems[i].Selected) {
int i2 = listView1.SelectedItems[i].Index;
item = listView1.Items[i2].Text;
sColl.Add(item);
}
}
}
listView1.SelectedItems.Clear();
foreach (var itemS in sColl)
{
string items = itemS;
}
sColl.Clear();
listView1.SelectedItems.Clear();
Why don't bring back the SelectedIndex ? Add this extension after your current namespace.
public static class Extension
{
public static int SelectedIndex(this ListView listView)
{
if (listView.SelectedIndices.Count > 0)
return listView.SelectedIndices[0];
else
return 0;
}
}
Encapsulate this class in a namespace called Extensions and then add this inside your projects namespace to use the extension.
using Extensions;
Then simply use like this
private void ListView1_SelectedIndexChanged(object sender, EventArgs e)
{
int selectionindex = ListView1.SelectedIndex();
ListViewItem seletedItem = ListView1.Items[selectionindex];
}
P.S.
The extension method should have returned -1 on Else, but as long as you're using it from the SelectedIndexChanged event, you're fine as it won't get fired if there are no items.
This is by design, as the SelectedIndexChanged event gets fired twice. Once to deselect the initial item, then to select the new one.
Proper way is to return -1 and check for negative numer.
This is also why someone here got and ArgumentOutOfRange Exception.
I have a BindingList< KeyValuePair < string, string > > that is bound to a ComboBox control. Based on some conditions, the BindingList will be added a new KeyValuePair. Now, the Newly added item shows up at index 0 of the Combobox, instead of at the end.
While debugging, I found that the BindingList has got the right order. (i.e, the new KeyValuePair is appended)
Also, I check the SelectedValue of the ComboBox in it's SelectedIndexChanged handler and it seems to be not of the ListItem that got selected. Instead, it is that of the supposed ListItem, if the ComboBox had got the right order as in its DataSource, - the BindingList..
The code is a small part of a large project.. Plz let me know if the question is not clear. I can put the relevant parts of the code as per our context.
How could something like this happen? What can I do differently?
I have this class something like this.
public class DropdownEntity
{
//removed all except one members and properties
private string frontEndName
public string FrontEndName
{
get {return this.frontEndName; }
set {this.frontEndName= value; }
}
//One Constructor
public DropdownEntity(string _frontEndName)
{
this.FrontEndName = _frontEndName;
//Removed code which initializes several members...
}
//All methods removed..
public override string ToString()
{
return frontEndName;
}
}
In my windows form, I have a tab control with several tabs. In one of the tabs pages, I have a DataGridView. The user is supposed to edit the cells and click on a Next - button. Then, some processing will be done, and the TabControl will be navigated to the next tab page.
The next tab page has the combobox that has the problem I mentioned. This page also has a back button, which will take back.. the user can modify the gridview cells again.. and click on the next button. This is when the order gets messed up.
I am posting here the Click event handler of the Next Button.. Along with the class, with the rest of the code removed.
public partial class AddUpdateWizard : Form
{
//Removed all members..
BindingList<KeyValuePair<string, string>> DropdownsCollection;
Dictionary<string, DropdownEntity> DropdownsDict;
//Defined in a partial definition of the class..
DataGridView SPInsertGridView = new DataGridView();
ComboBox DropdownsCmbBox = new ComboBox();
Button NextBtn2 = new Button();
Button BackBtn3 = new Button();
//Of course these controls are added to one of the panels
public AddUpdateWizard(MainForm mainForm)
{
InitializeComponent();
DropdownsDict = new Dictionary<string, DropdownEntity>();
}
private void NextBtn2_Click(object sender, EventArgs e)
{
string sqlArgName;
string frontEndName;
string fieldType;
for (int i = 0; i < SPInsertGridView.Rows.Count; i++)
{
sqlArgName = "";
frontEndName = "";
fieldType = "";
sqlArgName = SPInsertGridView.Rows[i].Cells["InsertArgName"].Value.ToString().Trim();
if (SPInsertGridView.Rows[i].Cells["InsertArgFrontEndName"].Value != null)
{
frontEndName = SPInsertGridView.Rows[i].Cells["InsertArgFrontEndName"].Value.ToString().Trim();
}
if (SPInsertGridView.Rows[i].Cells["InsertArgFieldType"].Value != null)
{
fieldType = SPInsertGridView.Rows[i].Cells["InsertArgFieldType"].Value.ToString().Trim();
}
//I could have used an enum here, but this is better.. for many reasons.
if (fieldType == "DROPDOWN")
{
if (!DropdownsDict.ContainsKey(sqlArgName))
DropdownsDict.Add(sqlArgName, new DropdownEntity(frontEndName));
else
DropdownsDict[sqlArgName].FrontEndName = frontEndName;
}
else
{
if (fieldType == "NONE")
nonFieldCount++;
if (DropdownsDict.ContainsKey(sqlArgName))
{
DropdownsDict.Remove(sqlArgName);
}
}
}
//DropdownsCollection is a BindingList<KeyValuePair<string, string>>.
//key in the BindingList KeyValuePair will be that of the dictionary.
//The value will be from the ToString() function of the object in the Dictionary.
DropdownsCollection = new BindingList<KeyValuePair<string,string>>(DropdownsDict.Select(kvp => new KeyValuePair<string, string>(kvp.Key, kvp.Value.ToString())).ToList());
DropdownsCmbBox.DataSource = DropdownsCollection;
DropdownsCmbBox.DisplayMember = "Value";
DropdownsCmbBox.ValueMember = "Key";
//Go to the next tab
hiddenVirtualTabs1.SelectedIndex++;
}
private void BackBtn3_Click(object sender, EventArgs e)
{
hiddenVirtualTabs1.SelectedIndex--;
}
//On Selected Index Changed of the mentioned Combobox..
private void DropdownsCmbBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (DropdownsCmbBox.SelectedValue != null)
{
if (DropdownsDict.ContainsKey((DropdownsCmbBox.SelectedValue.ToString())))
{
var dropdownEntity = DropdownsDict[DropdownsCmbBox.SelectedValue.ToString()];
DropdownEntityGB.Text = "Populate Dropdowns - " + dropdownEntity.ToString();
//Rest of the code here..
//I see that the Datasource of this ComboBox has got the items in the right order.
// The Combobox's SelectedValue is not that of the selected item. Very Strange behavior!!
}
}
}
}
The very first time the user clicks the Next Button, it's fine. But if he clicks the Back Button again and changes the Data Grid View cells.. The order will be gone.
I know, it can be frustrating to look at. It's a huge thing to ask for help. Any help would be greatly appreciated!
Please let me know if you need elaboration at any part.
Thanks a lot :)
I think you have two problems here.
First, if you want to retain the order of the items you should use an OrderedDictionary instead of a regular one. A normal collection will not retain the order of the items when you use Remove method. You can see more info about this related to List here.
You could use such dictionary like this:
DropDownDict = new OrderedDictionary();
// Add method will work as expected (as you have it now)
// Below you have to cast it before using Select
DropDownCollection = new BindingList<KeyValuePair<string, string>>(DropDownDict.Cast<DictionaryEntry>().Select(kvp => new KeyValuePair<string, string>(kvp.Key.ToString(), kvp.Value.ToString())).ToList());
The second problem could be that you change the display name (FrontEndName) of already existing items, but the key is preserved. When you add a new item, try to remove the old one that you're not using anymore and add a new item.
The Sorted Property of the Combobox is set to True! I didn't check that until now. I messed up. Terribly sorry for wasting your time Adrian. Thanks a lot for putting up with my mess here.. :)
This is kind of a oddball problem so I will try to describe the best that I can.
I have a DataGridView that shows a list of contracts and various pieces of information about them. There are three view modes: Contract Approval, Pre-Production, and Production. Each mode has it's own set of columns that need to be displayed.
What I have been doing is I have three radio buttons one for each contract style. all of them fire their check changed on this function
private void rbContracts_CheckedChanged(object sender, EventArgs e)
{
dgvContracts.Columns.Clear();
if (((RadioButton)sender).Checked == true)
{
if (sender == rbPreProduction)
{
dgvContracts.Columns.AddRange(searchSettings.GetPreProductionColumns());
this.contractsBindingSource.DataMember = "Preproduction";
this.preproductionTableAdapter.Fill(this.searchDialogDataSet.Preproduction);
}
else if (sender == rbProduction)
{
dgvContracts.Columns.AddRange(searchSettings.GetProductionColumns());
this.contractsBindingSource.DataMember = "Production";
this.productionTableAdapter.Fill(this.searchDialogDataSet.Production);
}
else if (sender == rbContracts)
{
dgvContracts.Columns.AddRange(searchSettings.GetContractsColumns());
this.contractsBindingSource.DataMember = "Contracts";
this.contractsTableAdapter.Fill(this.searchDialogDataSet.Contracts);
}
}
}
Here is the GetxxxColumns function
public DataGridViewColumn[] GetPreProductionColumns()
{
this.dgvTxtPreAccount.Visible = DgvTxtPreAccountVisable;
this.dgvTxtPreImpromedAccNum.Visible = DgvTxtPreImpromedAccNumVisable;
this.dgvTxtPreCreateDate.Visible = DgvTxtPreCreateDateVisable;
this.dgvTxtPreCurrentSoftware.Visible = DgvTxtPreCurrentSoftwareVisable;
this.dgvTxtPreConversionRequired.Visible = DgvTxtPreConversionRequiredVisable;
this.dgvTxtPreConversionLevel.Visible = DgvTxtPreConversionLevelVisable;
this.dgvTxtPreProgrammer.Visible = DgvTxtPreProgrammerVisable;
this.dgvCbxPreEdge.Visible = DgvCbxPreEdgeVisable;
this.dgvCbxPreEducationRequired.Visible = DgvCbxPreEducationRequiredVisable;
this.dgvTxtPreTargetMonth.Visible = DgvTxtPreTargetMonthVisable;
this.dgvCbxPreEdgeDatesDate.Visible = DgvCbxPreEdgeDatesDateVisable;
this.dgvTxtPreStartDate.Visible = DgvTxtPreStartDateVisable;
this.dgvTxtPreUserName.Visible = DgvTxtPreUserNameVisable;
this.dgvCbxPreProductionId.Visible = DgvCbxPreProductionIdVisable;
return new System.Windows.Forms.DataGridViewColumn[] {
this.dgvTxtPreAccount,
this.dgvTxtPreImpromedAccNum,
this.dgvTxtPreCreateDate,
this.dgvTxtPreCurrentSoftware,
this.dgvTxtPreConversionRequired,
this.dgvTxtPreConversionLevel,
this.dgvTxtPreProgrammer,
this.dgvCbxPreEdge,
this.dgvCbxPreEducationRequired,
this.dgvTxtPreTargetMonth,
this.dgvCbxPreEdgeDatesDate,
this.dgvTxtPreStartDate,
this.dgvTxtPreUserName,
this.dgvCbxPreProductionId,
this.dgvTxtCmnHold,
this.dgvTxtCmnConcern,
this.dgvTxtCmnAccuracyStatus,
this.dgvTxtCmnEconomicStatus,
this.dgvTxtCmnSoftwareStatus,
this.dgvTxtCmnServiceStatus,
this.dgvTxtCmnHardwareStatus,
this.dgvTxtCmnAncillaryStatus,
this.dgvTxtCmnFlowStatus,
this.dgvTxtCmnImpromedAccountNum,
this.dgvTxtCmnOpportunityId};
}
public DataGridViewColumn[] GetProductionColumns()
{
this.dgvcTxtProAccount.Visible = DgvTxtProAccountVisable;
this.dgvTxtProImpromedAccNum.Visible = DgvTxtProImpromedAccNumVisable;
this.dgvTxtProCreateDate.Visible = DgvTxtProCreateDateVisable;
this.dgvTxtProConvRequired.Visible = DgvTxtProConvRequiredVisable;
this.dgvTxtProEdgeRequired.Visible = DgvTxtProEdgeRequiredVisable;
this.dgvTxtProStartDate.Visible = DgvTxtProStartDateVisable;
this.dgvTxtProHardwareRequired.Visible = DgvTxtProHardwareReqiredVisable;
this.dgvTxtProStandardDate.Visible = DgvTxtProStandardDateVisable;
this.dgvTxtProSystemScheduleDate.Visible = DgvTxtProSystemScheduleDateVisable;
this.dgvTxtProHwSystemCompleteDate.Visible = DgvTxtProHwSystemCompleteDateVisable;
this.dgvTxtProHardwareTechnician.Visible = DgvTxtProHardwareTechnicianVisable;
return new System.Windows.Forms.DataGridViewColumn[] {
this.dgvcTxtProAccount,
this.dgvTxtProImpromedAccNum,
this.dgvTxtProCreateDate,
this.dgvTxtProConvRequired,
this.dgvTxtProEdgeRequired,
this.dgvTxtProStartDate,
this.dgvTxtProHardwareRequired,
this.dgvTxtProStandardDate,
this.dgvTxtProSystemScheduleDate,
this.dgvTxtProHwSystemCompleteDate,
this.dgvTxtProHardwareTechnician,
this.dgvTxtCmnHold,
this.dgvTxtCmnConcern,
this.dgvTxtCmnAccuracyStatus,
this.dgvTxtCmnEconomicStatus,
this.dgvTxtCmnSoftwareStatus,
this.dgvTxtCmnServiceStatus,
this.dgvTxtCmnHardwareStatus,
this.dgvTxtCmnAncillaryStatus,
this.dgvTxtCmnFlowStatus,
this.dgvTxtCmnImpromedAccountNum,
this.dgvTxtCmnOpportunityId};
}
public DataGridViewColumn[] GetContractsColumns()
{
this.dgvTxtConAccount.Visible = this.DgvTxtConAccountVisable;
this.dgvTxtConAccuracyStatus.Visible = this.DgvTxtConAccuracyStatusVisable;
this.dgvTxtConCreateDate.Visible = this.DgvTxtConCreateDateVisable;
this.dgvTxtConEconomicStatus.Visible = this.DgvTxtConEconomicStatusVisable;
this.dgvTxtConHardwareStatus.Visible = this.DgvTxtConHardwareStatusVisable;
this.dgvTxtConImpromedAccNum.Visible = this.DgvTxtConImpromedAccNumVisable;
this.dgvTxtConServiceStatus.Visible = this.DgvTxtConServiceStatusVisable;
this.dgvTxtConSoftwareStatus.Visible = this.DgvTxtConSoftwareStatusVisable;
this.dgvCbxConPreProductionId.Visible = this.DgvCbxConPreProductionIdVisable;
this.dgvCbxConProductionId.Visible = this.DgvCbxConProductionVisable;
return new System.Windows.Forms.DataGridViewColumn[] {
this.dgvTxtConAccount,
this.dgvTxtConImpromedAccNum,
this.dgvTxtConCreateDate,
this.dgvTxtConAccuracyStatus,
this.dgvTxtConEconomicStatus,
this.dgvTxtConSoftwareStatus,
this.dgvTxtConServiceStatus,
this.dgvTxtConHardwareStatus,
this.dgvCbxConPreProductionId,
this.dgvCbxConProductionId,
this.dgvTxtCmnHold,
this.dgvTxtCmnConcern,
this.dgvTxtCmnAccuracyStatus,
this.dgvTxtCmnEconomicStatus,
this.dgvTxtCmnSoftwareStatus,
this.dgvTxtCmnServiceStatus,
this.dgvTxtCmnHardwareStatus,
this.dgvTxtCmnAncillaryStatus,
this.dgvTxtCmnFlowStatus,
this.dgvTxtCmnImpromedAccountNum,
this.dgvTxtCmnOpportunityId};
}
The issue is when I check a button the first time, everything shows up ok. I choose another view, everything is ok. But when I click on the first view the columns are out of order (it is like they are in reverse order but it is not exactly the same). this happens only to the first page you click on, the other two are fine. You can click off and click back on as many times as you want after those initial steps, The first list you selected at the start will be out of order the other two will be correct.
Any ideas on what could be causing this?
EDIT--
Things I have found so far:
ColumnDisplayIndexChanged fires many many times (over 200 times) when I view the first selection a second time. if the function does nothing it still loads the page, if i put a dialog box to show it fired (it was a lot of clicks) eventually i either get a big red X in the data grid view area or it loads fine (depending on the page, I get a X for pre-production but the other two loads fine (the message box still shows up hundreds of times) when you select them first)
My best guess is that this.XXX.Fill is changing the DisplayIndex value if the change is occuring after the column range creation function has returned. There are a few things you could consider however.
Create the range of columns once rather than each time a different view is selected.
Is memory an issue? If the datasets are not large and should not be large in the future you could fill 3 seperate containers and change the binding to a different container rather than refilling a single container everytime.
I think I would at the very least create the column ranges only once rather than each time.
Edit
private DataGridViewColumns[] PreProducitonColumns {get;set;}
private DataGridViewColumns[] ProductionColumns {get;set;}
private DataGridViewColumns[] ContractsColumns {get;set;}
private void Form_Load()
{
this.PreProducitonColumns = searchSettings.GetPreProductionColumns();
this.ProductionColumns = searchSettings.GetProductionColumns();
this.ContractsColumns = searchSettings.GetContractsColumns();
}
private void rbContracts_CheckedChanged(object sender, EventArgs e)
{
dgvContracts.Columns.Clear();
if (((RadioButton)sender).Checked == true)
{
if (sender == rbPreProduction)
{
dgvContracts.Columns.AddRange(PreProducitonColumns);
this.contractsBindingSource.DataMember = "Preproduction";
this.preproductionTableAdapter.Fill(this.searchDialogDataSet.Preproduction);
}
else if (sender == rbProduction)
{
dgvContracts.Columns.AddRange(ProductionColumns);
this.contractsBindingSource.DataMember = "Production";
this.productionTableAdapter.Fill(this.searchDialogDataSet.Production);
}
else if (sender == rbContracts)
{
dgvContracts.Columns.AddRange(ContractsColumns);
this.contractsBindingSource.DataMember = "Contracts";
this.contractsTableAdapter.Fill(this.searchDialogDataSet.Contracts);
}
}
}
I took the easy way out. I just created 3 DataGridView and set them visible based off of the radio button.
I am creating sort of a "Navigation panel" (which is actually an ItemControl) for SL and using Regions to allow each module to add his link to the panel.
Problem is that modules loading is inconsistent and thus order of links in the panel can change according to modules loading order.
Restricting the modules order is out of the question.
Other feasible option is the order the region's Views Collection that is binded to the ItemControl, the problem is that ViewCollection is very limited, so ordering it is pretty hard.
Did I miss an option, do you have an idea?
Thanks
Ariel
In Prism4 you just apply the ViewSortHintAttribute to your views:
[ViewSortHint("100")]
class FirstView : UserControl { }
[ViewSortHint("200")]
class SecondView : UserControl { }
The default sort comparer on the regions will pick up this attribute and sort the views accordingly. You can put any string into the attribute but I tend to use medium sized numbers that allow me to easily put a new view in between existing ones.
Refering to Sam's answer you first have to build your comparer. The following one is also capable of views that do not have a dedicated wish to be positioned at. To attach this comparer to the region that has to be sorted you can use a way intruduced by the prism manual:
public partial class MainView : UserControl
{
public MainView( )
{
InitializeComponent( );
ObservableObject<IRegion> observableRegion = RegionManager.GetObservableRegion( ContentHost );
observableRegion.PropertyChanged += ( sender, args ) =>
{
IRegion region = ( (ObservableObject<IRegion>)sender ).Value;
region.SortComparison = CompareViews;
};
}
private static int CompareViews( object x, object y )
{
IPositionView positionX = x as IPositionView;
IPositionView positionY = y as IPositionView;
if ( positionX != null && positionY != null )
{
//Position is a freely choosable integer
return Comparer<int>.Default.Compare( positionX.Position, positionY.Position );
}
else if ( positionX != null )
{
//x is a PositionView, so we favour it here
return -1;
}
else if ( positionY != null )
{
//y is a PositionView, so we favour it here
return 1;
}
else
{
//both are no PositionViews, so we use string comparison here
return String.Compare( x.ToString( ), y.ToString( ) );
}
}
}
At least in prism V4 there you can tell the region manager how to sort the views in a specific region. You just need to provide a compare function to the region.
This example sorts by a very stupid value, the function name:
private static int CompareViews(object x, object y)
{
return String.Compare(x.ToString(), y.ToString());
}
this._regionManager.Regions["MyRegion"].SortComparison = CompareViews;
Of course the region needs to be known to the region manager before you can set the SortComparison. So far the only workaround I found to achieve this was to defer to set the comparison function using the Dispatcher:
private readonly IRegionManager _regionManager;
[ImportingConstructor]
public ShellViewModel(IRegionManager regionManager)
{
this._regionManager = regionManager;
Dispatcher dp = Dispatcher.CurrentDispatcher;
dp.BeginInvoke(DispatcherPriority.ApplicationIdle, new ThreadStart(delegate
{
if (this._regionManager.Regions.ContainsRegionWithName("MyRegion"))
this._regionManager.Regions["MyRegion"].SortComparison = CompareViews;
}));
}
Of course you should use some more useful information than the class name for the sorting order, but this should be easy to solve.
This is not built into Prism regions, however it's easily implementable.
Damian Schenkelman has posted an extension method he created for adding a region to an index that seems to work pretty well.
http://blogs.southworks.net/dschenkelman/2009/03/14/how-to-add-a-view-to-a-region-in-a-particular-index-with-prism-v2/
Hope this helps.
I found that Sam's solution worked, but discovered that it executes the sort when all views have been added to the region, thus sorting the views twice.
Although it is still a valid solution, reading this post in Prism discussion made me think about a way of implementing this just when the region has been loaded, but before any views have been added yet.
1 - Subscribe to the CollectionChanged of Regions collection
I placed this in the Shell ViewModel code which is the one associated to the View that contains the region I want to sort. Whenever the IRegionManager import has been resolved I subscribe to the CollectionChanged event of its Regions collection:
this._regionManager.Regions.CollectionChanged +=
new NotifyCollectionChangedEventHandler(Regions_CollectionChanged);
2 - Change the SortComparison of the region in the event delegate
Then the delegate Regions_CollectionChanged will execute whenever the Regions collection is updated and will change the SortComparison of my desired region:
void Regions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var o in e.NewItems)
{
IRegion region = o as IRegion;
if (region != null && region.Name == RegionNames.NavigationRegion)
{
region.SortComparison = CompareNavigatorViews;
}
}
}
}
3 - Define the CompareNavigatorViews delegate
In my case, I just sort the views by the title of the assembly where they are contained, you can implement your own compare method here. Remember that the objects you'll receive here are the Views and not the ViewModels.
private static int CompareNavigatorViews(object x, object y)
{
if (x == null)
if (y == null)
return 0;
else
return -1;
else
if (y == null)
return 1;
else
{
AssemblyInfo xAssemblyInfo = new AssemblyInfo(Assembly.GetAssembly(x.GetType()));
AssemblyInfo yAssemblyInfo = new AssemblyInfo(Assembly.GetAssembly(y.GetType()));
return String.Compare(xAssemblyInfo.Title, yAssemblyInfo.Title);
}
}
Just in case somebody asks, the AssemblyInfo class is an utility class I made. To get the title of an assembly you could use this function:
string GetAssemblyTitle(Assembly assembly)
{
object[] attributes = assembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
if (attributes.Length == 1)
{
return (attributes[0] as AssemblyTitleAttribute).Title;
}
else
{
// Return the assembly name if there is no title
return this.GetType().Assembly.GetName().Name;
}
}
Hope this helps someone!
Well as the lack of answers counting. I have not found a solution with Prism.
Instead I've used MEF to solve this.
I will write a blog post on it and update this place holder.