I'm new to programming and I'm trying to restrict users deleting every field for a custom barcode configuration window which looks like this:
And this is the code that we are using to delete those fields
public BarcodeViewModel(IEnumerable<BatchTypeBarcodeConfig> barcode)
{
var barcodeDescription = new StringBuilder(64);
int sequenceNumber = -1;
Fields.AddRange(
barcode
?.Where(field => field != null)
.OrderBy(field => field.StartPosition)
.Select(field =>
{
sequenceNumber++;
if (field.FieldType == FieldTypeCode.STATIC)
{
barcodeDescription.Append(field.FieldText);
}
else
{
barcodeDescription.Append("(");
barcodeDescription.Append(field.FieldName);
barcodeDescription.Append(")");
}
var newField = new BarcodeFieldViewModel(field, sequenceNumber);
newField.FieldLayoutChangedEvent += BarcodeFieldViewModel_FieldLayoutChangedEvent;
return newField;
})
?? Enumerable.Empty<BarcodeFieldViewModel>());
Description = barcodeDescription.ToString();
}
public void Delete(BarcodeFieldViewModel field)
{
field.FieldLayoutChangedEvent -= BarcodeFieldViewModel_FieldLayoutChangedEvent;
Fields.Remove(field);
UpdateFieldPositionValues();
}
by this method, users can delete every barcode config field. Is there any way to restrict users to delete every barcode field? there should be at least one barcode field in the fields window.
Any help is appreciated.
If Fields is a List<BarcodeFieldViewModel>, then you can add simple validation to your Delete method:
if (Fields.Count > 1) {
field.FieldLayoutChangedEvent -= BarcodeFieldViewModel_FieldLayoutChangedEvent;
Fields.Remove(field);
UpdateFieldPositionValues();
}
You could also use this same logic to determine if the delete button should even be visble.
Related
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.
I am currently working in winforms c#. I have a string "Flippo1SN" and that string is determined in my form. "Flippo1SN" can change in 'serial number names' that matches the same name as in Properties.Settings . The value of the serial numbers in Properties.Settings are integers. I want to use an if statement without summing up all the integers in my Properties.Settings, instead, I want to use a string so I could write my code in 1 if statement instead of more. I tried to use a code like this:
if (Properties.Settings.Default.Flippo1SN == 0) {}
Which, as you may have guessed, did not work. I've tried more ways as:
if (Properties.Settings.Default.(Flippo1SN) == 0) {}
if (Properties.Settings.Default.("Flippo1SN") == 0) {}
if (Properties.Settings.Default.[Flippo1SN] == 0) {}
if (Properties.Settings.Default.["Flippo1SN"] == 0) {}
It gives me an error saying that there is an indentifier expected.
How can I solve this? This question may have already been asked in the past but I couldn't find it.
Thanks in advance.
Edit 1:
Flippo1SN Does not exist in Properties.Settings, it is its value that does. Its value is something like: GF01, GF02, ... I am trying to refer to those.
Edit 2:
These are the variables I am trying to refer to. Flippo1SNs name changes to GF01 or GF02 etc. I don't want to put a code like this:
if (Flippo1SN == "GF01")
{
if (Properties.Settings.Default.GF01 == 0) {//Do action}
}
Instead, I want to refer to GF01 immediately by using Flippo1SN in the second if statement. That would cost me a lot of time and writing because I have a lot of Integers in Properties.Settings .
Edit 3
I'm going to explain what I am creating so you guys understand what I'm doing.
I am creating a 'Collecting Game' where you collect Flippos (Pogs or milk caps is what it's called in English I guess?). To get those Flippos, you open a giftbox and receive 3 random flippos. The output is something like this:
Screenshot ("Verzamel" means collect)
In this image, you see 3 flippos. In the top left corner, you see the Serial Numbers of each flippo (MF06, GF16, OF12). 'MF' stands for 'Mega Flippo', 'GF' for Green Flippo and 'OF' stands for 'Orange Flippo'.
In the code, I have used random to choose which one you get (50% chance for green, 30% chance for orange and 20% chance for mega. The percentage is determined by the amount of a specific group). I have also 3 strings in my code that holds the serial number of these flippos (Flippo1SN, Flippo2SN, and Flippo3SN). These serial numbers refer to the ones in my database (or just the properties.settings tab). In this scenario, MF06, GF16 and OF12 increments by 1.
Now, I want to check if you've received a flippo you didn't have before. If you do so, a label will appear above the picture and the text of that label will be "New".
To do so, I first need to check which one you have received, then check if you already have that in your database. The first if checks if you have received GF01 and the second if checks if you already have it:
if (Flippo1SN == "GF01")
{
if (Properties.Settings.Default.GF01 == 0)
{
label1.Show();
}
}
else if (FLippo1SN == "GF02") {//ETC}
Flippo1SN is already determined. I am not trying to change the Flippo1SNs value. I am just using this string to check which flippo you have received. All flippos have a serial number and Flippo1SN holds a serial number to refer to which flippo you have received.
What I now am asking is, is there a more fast way to do this? Can't I use the value of Flippo1SN immediately in an if statement so I could avoid multiple if statements?
I really hope I made things clear now.
First you have to check if it exists then you can do whatever you want with it:
if(Properties.Settings.Default.ContainsKey(Flippo1SN))
{
if(Properties.Settings.Default[Flippo1SN] == 0)
{
// ....
}
}
You can also write handlers to get rid of the if checks:
interface ISettingsHandler
{
void Handle(int value);
bool CanHandle(string name);
}
class GF1Handler : ISettingsHandler
{
public void Handle(int value){
// do action
}
public bool CanHandle(string propertyName){
return propertyName.Equals("GF1");
}
}
class GF2Handler : ISettingsHandler
{
public void Handle(int value){
// do action
}
public bool CanHandle(string propertyName){
return propertyName.Equals("GF2");
}
}
You can then initialize a list of handlers, and use the one that can handle the selected property:
var handler = listOfHandler.FirstOrDefault(h => h.CanHandle(Flippo1SN))
if( != null)
handler.Handle(Properties.Settings.Default[Flippo1SN]);
By using Properties.Settings.Default you can retrieve all the properties in your Project.
Then, by iterating through them you can check the Name of each property to see if it matches your Flippo1SN like so:
string Flippo1SN = "GF02";
var props = Properties.Settings.Default;
foreach(var prop in props.Properties)
{
var settingProperty = (SettingsProperty)prop;
if (settingProperty.Name == Flippo1SN)
{
// Now you found the property that matches Flippo1SN.
// Get its value.
var value = settingProperty.DefaultValue;
}
}
Edit:
How to check if value of the property is zero:
string Flippo1SN = "GF02";
foreach (SettingsProperty prop in Properties.Settings.Default.Properties)
{
if (prop.Name == Flippo1SN)
{
if (int.TryParse(prop.DefaultValue.ToString(), out int result))
{
if (result == 0)
{
// The value is zero.
}
}
}
}
EDIT: NEW CODE AND SCREENSHOTS
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
dynamic props = Properties.Settings.Default;
string Flippo1SN = "200";
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
string output = "Properties.Settings.Default.test : ";
if (Flippo1SN == props.test.ToString())
{
output += $"{props.test}";
}
if (Flippo1SN == props.test2.ToString())
{
output += $"{props.test2}";
}
if (Flippo1SN == props.test3.ToString())
{
output += $"{props.test3}";
}
MessageBox.Show(output);
}
}
}
I'm relatively new to C# and have spent an inordinate amount of time trying to figure this out myself with no luck. Hoping you guys can help.
I have 2 windows forms. In the first form, the user enters a citation number. I want to take that citation number, search for it in an external text file, and then return all of the data in the row for that citation into separate textboxes.
The text file looks something like this:
S8729936 , 6JXV123 , 10/1/2015 , 10/31/2015 , PAID , 49.5
A7472601 , 2NXP234 , 10/12/2015 , 11/11/2015 , UNPAID , 99
W2041810 , 5JPB345 , 10/19/2015 , 11/18/2015 , UNPAID , 99
And the second form has 6 textboxes. I have it so that the citation number, let's say S8729936 is passed into the first textbox, but I cannot seem to figure out how to then search the text file for S8729936 and give me the rest of the data in the row inside the textboxes.
Here are some examples of things I've tried. I've been copying and pasting and then messing with code all day, so if the details don't seem to match, that's probably the reason.
public Form2(string citation)
{
InitializeComponent();
txtCitation2.Text = citation;
const string FILENAME = #"\Path\ProjectData.txt";
FileStream fsInFile = new FileStream(FILENAME, FileMode.Open, FileAccess.Read);
StreamReader srReader = new StreamReader(fsInFile);
const char CH_DELIM = ',';
string strRecordIn;
string[] strFields;
if (strFields != null)
{
strRecordIn = srReader.ReadLine();
strFields = strRecordIn.Split(CH_DELIM);
txtLicense2.Text = strFields[1];
}
strRecordIn = srReader.ReadLine();
srReader.Close();
fsInFile.Close();
}
No luck there, how about something along the lines of this:
string whole_file = File.ReadAllText(#"Path\ProjectData.txt");
whole_file = whole_file.Replace('\n', '\r');
string[] lines = whole_file.Split(new char[] { '\r' });
int num_rows = lines.Length;
int num_cols = lines[0].Split(',').Length;
string[,] values = new string[num_rows, num_cols];
for (int r = 0; r < num_rows; r++)
{
string[] line_r = lines[r].Split(',');
for (int c = 0; c < num_cols; c++)
{
values[r, c] = line_r[c];
}
}
txtLicense2.Text = lines[1];
Nope. Maybe something along the lines of this:
const string FILENAME = #"C:\Users\rfranklin\Documents\ProjectData.txt";
FileStream fsinfile = new FileStream(FILENAME, FileMode.Open, FileAccess.Read);
StreamReader srReader = new StreamReader(fsinfile);
const string CH_DELIM = " ,";
string strRecordIn;
string[] strFields = new string[10];
string citnum = citation;
bool found = false;
strRecordIn = srReader.ReadLine();
foreach(string x in strFields)
{
if (x == citation)
{
found = true;
break;
}
}
if (found)
{
txtLicense2.Text = strFields[1];
}
Still no luck. And on and on. It seems as though I'm mostly missing how to tell the program what to search for and I am not sure what else to do. Like I said, I've been Googling various ways to do it all day, but I can't seem to make anything work right.
I'm doing this in Visual Studio 2013, if that helps.
Any help would be immensely appreciated. Thanks.
If the number of lines in the CSV file is not too large (I wouldn't know what "too large" is), then you could leverage a few .NET constructs, such as Data Binding and Linq to achieve this.
For starters, I would create a class that implements INotifyPropertyChanged:
namespace Citations
{
public class Citation : INotifyPropertyChanged
{
public static Citation ParseLine(string line)
{
Citation cit = new Citation();
if (string.IsNullOrEmpty(line))
throw new ArgumentException("Invalid argument", nameof(line));
string[] vals = line.Split(',');
if (vals.Length != 6)
throw new ArgumentOutOfRangeException(nameof(line), "Invalid format");
cit.CitationNumber = vals[0].Trim();
cit.PlateNumber = vals[1].Trim();
cit.DateCreated = DateTime.Parse(vals[2].Trim());
cit.DateExpired = DateTime.Parse(vals[3].Trim());
cit.Status = vals[4].Trim();
cit.Amount = Decimal.Parse(vals[5].Trim());
return cit;
}
void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
string citationNumber;
public string CitationNumber
{
get
{
return citationNumber;
}
set
{
if (citationNumber != value)
{
citationNumber = value;
RaisePropertyChanged(nameof(CitationNumber));
}
}
}
string plateNumber;
public string PlateNumber
{
get
{
return plateNumber;
}
set
{
if (plateNumber != value)
{
plateNumber = value;
RaisePropertyChanged(nameof(PlateNumber));
}
}
}
DateTime dateCreated;
public DateTime DateCreated
{
get
{
return dateCreated;
}
set
{
if (dateCreated != value)
{
dateCreated = value;
RaisePropertyChanged(nameof(DateCreated));
}
}
}
DateTime dateExpired;
public DateTime DateExpired
{
get
{
return dateExpired;
}
set
{
if (dateExpired != value)
{
dateExpired = value;
RaisePropertyChanged(nameof(DateExpired));
}
}
}
string status;
public string Status
{
get
{
return status;
}
set
{
if (status != value)
{
status = value;
RaisePropertyChanged(nameof(Status));
}
}
}
Decimal amount;
public Decimal Amount
{
get
{
return amount;
}
set
{
if (amount != value)
{
amount = value;
RaisePropertyChanged(nameof(Amount));
}
}
}
}
}
This class is responsible for splitting a line from the CSV file, and converting it to an object of type Citation, which will be used to data bind the textboxes in Form2 later on.
Then in the first Form, I would simply read the file, and using some Linq operators, convert the file to a Dictionary of Citation objects, the key of the Dictionary being the Citation number:
private void button1_Click(object sender, EventArgs e)
{
string[] allLines = File.ReadAllLines("Citations.csv");
Dictionary<string, Citation> dict = allLines.Select(l => Citation.ParseLine(l)).ToDictionary(c => c.CitationNumber, k => k);
Citation cit = dict["W2041810"];
Form2 frm2 = new Form2();
frm2.SetCitation(cit);
frm2.ShowDialog();
}
In the code above, we're using the ToDictionary Linq operator to create a Disctionary from your Citation objects, and the Dictionat key is the Citation number.
Here I'm hardcoding one of the citations for lookup and passing to Form2, which would have a SetCitation method like this:
public void SetCitation(Citation citation)
{
this.citationBindingSource.DataSource = citation;
}
The code to Form2 is a bit difficult to show because I have used the Form designer to setup the data binding for each TextBox, and if I wanted to show that, I'd basically have to show the whole Form2.Designer.cs file.
Instead, I propose to guide you through the process of creating a Project DataSource, then drag & drop the TextBoxes onto Form2 from the Data Sources dialog in Visual Studio.
So, after adding the Citation class to your solution, make sure to compile at leat once so that the "Add data source" wizard will pick that class up as a possible data source.
Then, make sure the Data Sources dialog is displayed by going to View > Other Windows > Data Sources (assuming Visual Studio 2015 here).
From the Data Sources dialog, click the "Add New Data Source button" tolaunch the Data SOurce Configuration Wizard. From the list of possible data sources, you will choose "Object":
Then click the Next button. From the next Wizard step, you will select the Citation class:
and then click the Finish button.
In the Data Sources dialog, you should now have something like this:
From this Data Sources dialog, you can now drag & drop the individual fields onto Form2, which should give you something like this:
You will also notice in the Component tray of Form2, a BindingSource object has been added:
Under the hood, Visual Studio will have set all the Data Binding for your Citation object properties to be displayed in the corresponding TextBox.
This is the "glue" that makes it possible to call Form2.SetCitation() with a Citation object, and have all the fields displayed where they should.
I know this is quite a mouthful to chew on, but believe me, once you understand the principles behind this, you will not want to go back to the kind of spagethi code that you started implementing (no offense, we've all been there).
If you would like me to clarify any specific section of my answer, just let me know, and I'll edit accordingly.
Cheers
A simple winForm version:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
List<string> matchedList = new List<string>();
public Form1(string citation)
{
InitializeComponent();
string filePath = #"C:\Users\rfranklin\Documents\ProjectData.txt";
string[] linesArr = File.ReadAllLines(filePath);
//find matches
foreach(string s in linesArr)
{
if(s.Contains(citation))
{
matchedList.Add(s); //matched
}
}
//output
foreach(string s in matchedList)
{
Console.WriteLine(s); //write to console
//or output to wherever you wish, eg.
//richTextBox.Text += s + "\n";
}
}
}
}
Note that Form1 needs to be called from somewhere else and take in citation argument. To test it standalone, change it to
public Form1()
{
InitializeComponent();
string citation = "S8729936";
The suggestion from #interceptwind did it for me. Inside the //output section of the code he provided, I basically just created a second array from the matched line, with the elements separated by the comma. The code looks like this:
//output
foreach (string s in matchedList)
{
string citationLine = s;
string[] lineData = citationLine.Split(',');
txtLicense2.Text = lineData[1];
txtIssued2.Text = lineData[2];
txtDue2.Text = lineData[3];
txtStatus2.Text = lineData[4];
txtAmount2.Text = lineData[5];
}
This allowed me to put the data in the textboxes I needed. Thank you all for the assistance!
When a user uses the "Insert Link" feature on the RTE to create stories, we get something like...<Item-Name-Of-Story
Instead of taking the Item name I would like to use another field called "Headline"
Does anyone know how to do this?...
Headline-Of-Story
Any help will be much appreciated. Thanks
First of all, you need need to look at this class with Reflector or DotPeek : Sitecore.Shell.Controls.RichTextEditor.InsertLink.InsertLinkForm and to modify it with your own class.
You need to modify just this method,I tested and works fine :
protected override void OnOK(object sender, EventArgs args)
{
Assert.ArgumentNotNull(sender, "sender");
Assert.ArgumentNotNull((object) args, "args");
string displayName;
string text;
if (this.Tabs.Active == 0 || this.Tabs.Active == 2)
{
Item selectionItem = this.InternalLinkTreeview.GetSelectionItem();
if (selectionItem == null)
{
SheerResponse.Alert("Select an item.", new string[0]);
return;
}
else
{
displayName = selectionItem["Headline"];
if (selectionItem.Paths.IsMediaItem)
text = CustomInsertLinkForm.GetMediaUrl(selectionItem);
else if (!selectionItem.Paths.IsContentItem)
{
SheerResponse.Alert("Select either a content item or a media item.", new string[0]);
return;
}
else
{
LinkUrlOptions options = new LinkUrlOptions();
text = LinkManager.GetDynamicUrl(selectionItem, options);
}
}
}
else
{
MediaItem mediaItem = (MediaItem) this.MediaTreeview.GetSelectionItem();
if (mediaItem == null)
{
SheerResponse.Alert("Select a media item.", new string[0]);
return;
}
else
{
displayName = mediaItem.DisplayName;
text = CustomInsertLinkForm.GetMediaUrl((Item) mediaItem);
}
}
if (this.Mode == "webedit")
{
SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(text));
base.OnOK(sender, args);
}
else
SheerResponse.Eval("scClose(" + StringUtil.EscapeJavascriptString(text) + "," + StringUtil.EscapeJavascriptString(displayName) + ")");
}
After you modify this class you need to modify next file:
\sitecore\shell\Controls\Rich Text Editor\InsertLink\InsertLink.xml where you need to change codeBeside section
<CodeBeside Type="Sitecore.Shell.Controls.RichTextEditor.InsertLink.InsertLinkForm,Sitecore.Client"/>
with something like :
<CodeBeside Type="YourNameSpace.YourInsertLinkForm,YourAssembly"/>
The simplest way around this would be to type the desired link text, then select this before clicking 'insert link' - this way your hyperlink will have the text of whatever you entered, instead of defaulting to the item name.
If you want to modify how Sitecore renders links in RTE fields, you would need to modify the <renderField> pipeline - if you search for this in the web.config, you will see the different classes involved here. Using dotPeek you can decompile the Sitecore source to see how this works. Potentially you could then create your own renderField pipeline handler to change the link rendering behaviour and then reference this new class in your web.config.
I am having some trouble with getting the formatting to occur correctly. I believe it stems from what is probably incorrect understanding of the events that I am attempting to make the changes in.
any direction would be great
private void so_FetchData(object sender, FetchEventArgs eArgs)
{
if (m_so != null && m_so.Rows.Count > (m_soRowCount + 1))
{
DataRow soDr = m_so.Rows[m_soRowCount++];
if (soDr != null)
{
var compResID = (int) soDr["CompResID"];
var result = (ComplianceLevel) soDr["Result"];
var sectNum = (int) soDr["JobSectType"];
var sectName = soDr["S" + sectNum + "Name"] as string;
var sectTxt = soDr["S" + sectNum + "Text"] as string;
Fields["CompLev"].Value = (result == ComplianceLevel.OtherThanSerious) ? "Other Than Serious" : result.ToString();
m_sectInfo = new SectInfo(sectName, sectTxt);
m_causes = new Causes(compResID);
m_actions = new Actions(compResID);
subReport1.Report = m_sectInfo;
subReport2.Report = m_causes;
subReport3.Report = m_actions;
eArgs.EOF = false;
}
}
else
{
eArgs.EOF = true;
}
}
private void eh_BeforePrint(object sender, EventArgs e)
{
//decide where the bottom border should be draw to
if (m_actions != null && m_actions.ShouldShowBottBorder)
{
subReport3.Border.BottomStyle = BorderLineStyle.ThickSolid;
subReport2.Border.BottomStyle = BorderLineStyle.Solid;
}
else if (m_causes != null && m_causes.ShouldShowBottBorder)
{
subReport2.Border.BottomStyle = BorderLineStyle.ThickSolid;
}
else
{
subReport1.Border.BottomStyle = BorderLineStyle.ThickSolid;
}
}
the issue is that every time I step through the eh_BeforePrint method, the values always equate to false even though I step through the sub reports and the values are properly set. What is happening to cause the bool property to reset to false?
Just changing it if there are any records to print in the Fetch_Data method of each sub report.
private void Causes_FetchData(object sender, FetchEventArgs eArgs)
{
if (m_pos < m_corrs.Count)
{
if (!ShouldShowBottBorder)
ShouldShowBottBorder = true;
//...
}
}
You cannot be assured that the BeforePrint event raises exactly after the corresponding FetchData event. For example, FetchData may fire many times for several records, but due to some keep together logic in the layout engine, it may take several records before ActiveReports knows which page it will commit a section to. Therefore, it is pretty common for FetchData to be raised for several events before the corresponding BeforePrint events are raised.
If I understand your code properly there is a bigger problem though. It appears you are calculating values in your subreports (m_causes and m_actions appear to be actual subreports). If that is the case you cannot reliably calculate values in your subreports and pass them out to the parent report. Instead, you'll need to calculate those values in your parent report. However, usually you can add some shared function to calculate the value and call it from the parent report, and then pass that value into the subreports.
Comment here with some more information if you have specific questions about doing that.
On an unrelated note, you can get a pretty significant performance boost if you change the way you're initializing your subreports. Always initialize your subreports in the ReportStart event and then set their data in the format event of the section containing the Subreport control. This way you initialize each subreport once instead of initializing each subureport for every record. For example:
private void so_ReportStart()
{
subreport1.Report = new SectInfo();
subreport2.Report = new Causes();
subreport3.Report = new Actions();
}
private void Detail_Format()
{ // assuming Detail is the section containing your subreports:
((SectInfo)subreport1.Report).SetParameters(Fields["sectName"].Value, Fields["sectTxt"].Value);
((Causes)subreport2.Report).SetParameters(Fields["compResID"].Value);
((Actions)subreport3.Report).SetParameters(Fields["compResID"].Value);
}
You would setup those "Fields" values in FetchData similar to how your initializing the subreports now. Something like the following:
private void so_FetchData(object sender, FetchEventArgs eArgs)
{
if (m_so != null && m_so.Rows.Count > (m_soRowCount + 1))
{
DataRow soDr = m_so.Rows[m_soRowCount++];
if (soDr != null)
{
var compResID = (int) soDr["CompResID"];
var result = (ComplianceLevel) soDr["Result"];
var sectNum = (int) soDr["JobSectType"];
var sectName = soDr["S" + sectNum + "Name"] as string;
var sectTxt = soDr["S" + sectNum + "Text"] as string;
Fields["CompLev"].Value = (result == ComplianceLevel.OtherThanSerious) ? "Other Than Serious" : result.ToString();
/** BEGIN NEW CODE **/
Fields["sectName"].Value = sectName;
Fields["sectTxt"].Value = sectTxt;
Fields["compResID"].Value = compResId;
/** END NEW CODE **/
/** OLD CODE:
m_sectInfo = new SectInfo(sectName, sectTxt);
m_causes = new Causes(compResID);
m_actions = new Actions(compResID);
subReport1.Report = m_sectInfo;
subReport2.Report = m_causes;
subReport3.Report = m_actions;
**/
eArgs.EOF = false;
}
}
else
{
eArgs.EOF = true;
}
}
To learn more about the events in ActiveReports see the Report Events concepts topic in the ActiveReports Online Help.
To learn more about passing data into Subreports see Subreports with Run-Time Data Sources in the ActiveReports Online Help.
Scott Willeke
GrapeCity inc.