|
Sorry, i only wanted the relevant parts of my code. I messed up and should have reread before posting.
I made some corrections and added a few lines.
Hope it will make more sense.
If you want to, http://users.telenet.be/simonsmeets/source.zip[^] holds the project.
thx 4 your time...
simon
|
|
|
|
|
That still doesn't help. I have no idea what you're trying to bind the DGV to, or what you're trying to display, or why, ...
|
|
|
|
|
Ok, i'll start over.
The goal of the application is to check visual foxpro tables of an application named omniwin for numerous different problems. The important one for this problem is the analysis of the index key's uniqueness.
First the user selects the root of omniwin. After the root is validated this application checks the data tables for double index keys.
All the tables wich have to be checked can be found in a table called tableinfo (tableinfo.dbf) and from that table i take the tablename, index field of table, and location of the file in which it is stored. These variables are stored in an instance of ActiveTable, named tableToAnalyze.
Next the table is analysed by invoking tableToAnalyze.CheckForDoubleKey().
That method checks number of rows (tableToAnalyze.tableRecords) and number of distinct(id) rows (tableToAnalyze.tableUniqueRecords).
If those values do not match the method stores the not distinct keys in a queue (tableToAnalyze.doubleKey)
When the table is checked and tableToAnalyze.doubleKey.count() > 1 the instance tableToAnalyse is added to the BindingList<activetable> listOfDoubleIndexTables.
My intention is to display the BindingList listOfDoubleIndexTables in a DGV on the form. Then I can delete rows before starting a fix method because not all problems should be fixed.
Problem:
I've added a DGV on the form (desinger) and named it: dataGridDoubleKey
I've set the DataSource of the DGV to the appropriate datasource: dataGridDoubleKey.datasource=listOfDoubleIndexTables;
I've set the AutoGenerateColumns property to true.
But the DGV does not create columns and ofcourse does not display any rows.
So i thought, i'll define the rows myself.
i set the the AutoGenerateColumns property to false and defined the colums as such:
//dataGridDoubleKey.Columns.Add("omniwinRoot", "locatie");
//dataGridDoubleKey.Columns[0].Visible = false;
//dataGridDoubleKey.Columns.Add("table", "Table");
//dataGridDoubleKey.Columns.Add("tableKey", "Key");
//dataGridDoubleKey.Columns.Add("tableRecords", "Table records");
//dataGridDoubleKey.Columns[3].Visible = false;
//dataGridDoubleKey.Columns.Add("tableUniqueRecords", "Table Unique Records");
//dataGridDoubleKey.Columns[4].Visible = false;
//dataGridDoubleKey.Columns.Add("diff", "Number of double keys");
//dataGridDoubleKey.Columns.Add("doubleKey", "Queue double keys");
//dataGridDoubleKey.Columns.Add("noNversion", "Queue version id");
//dataGridDoubleKey.Columns[7].Visible = false;
W/o the comment ofcourse. Now i did have columns. And i saw that rows were added to the DGV each time a tableToAnalyze was added tot the bindinglist.
Unfortunately the rows of the DGV were alle emtpy.
My question, what am i doing wrong?
The code:
<br />
using System;<br />
using System.Collections.Generic;<br />
using System.ComponentModel;<br />
using System.Data;<br />
using System.Drawing;<br />
using System.Text;<br />
using System.Windows.Forms;<br />
using System.Windows.Forms.Design;<br />
using System.IO;<br />
using System.Data.OleDb;<br />
<br />
<br />
<br />
namespace nomni<br />
{<br />
<br />
public partial class FormAlgemeen : Form<br />
{<br />
string omniwinRoot;<br />
bool rootIsValid = false;<br />
BindingList<ActiveTable> listOfDoubleIndexTables = new BindingList<ActiveTable>();<br />
BindingList<ActiveTable> listOfNoNversionid = new BindingList<ActiveTable>();<br />
BindingList<string> tableFailedToAnalyze = new BindingList<string>();<br />
<br />
public FormAlgemeen()<br />
{<br />
InitializeComponent();<br />
dataGridDoubleKey = new DataGridView();<br />
dataGridVersionIdProblem = new DataGridView();<br />
<br />
dataGridDoubleKey.DataSource = listOfDoubleIndexTables;<br />
dataGridVersionIdProblem.DataSource = listOfNoNversionid;<br />
<br />
dataGridDoubleKey.AutoGenerateColumns = true;<br />
dataGridVersionIdProblem.AutoGenerateColumns = true;<br />
}<br />
<br />
private void chooseStandardOmniwin(object sender, EventArgs e)<br />
{ <br />
omniwinRoot = "C:\\Program Files\\Omegasoft\\medical\\omniwin\\";<br />
rootIsValid = ValidateOmniWinRoot(omniwinRoot);<br />
if (!rootIsValid)<br />
{<br />
MessageBox.Show("Default directory is not correct.");<br />
}<br />
else<br />
{<br />
this.stripStatusLabel1.Text = "Selected Omniwin Path: " + omniwinRoot;<br />
this.Refresh();<br />
}<br />
<br />
}<br />
<br />
private void browseToOmniwin(object sender, EventArgs e)<br />
{<br />
DialogResult answer;<br />
FolderBrowserDialog browseFolder = new FolderBrowserDialog();<br />
browseFolder.Description = "Select the Omniwin folder";<br />
browseFolder.RootFolder = Environment.SpecialFolder.MyComputer;<br />
answer = browseFolder.ShowDialog();<br />
omniwinRoot = browseFolder.SelectedPath + "\\";<br />
if (omniwinRoot != "" && answer != DialogResult.Cancel)<br />
{<br />
rootIsValid = ValidateOmniWinRoot(omniwinRoot);<br />
if (!rootIsValid)<br />
MessageBox.Show("You chose an incorrect folder path.");<br />
else<br />
{<br />
this.stripStatusLabel1.Text = "Selected Omniwin Path: " + omniwinRoot;<br />
this.Refresh();<br />
}<br />
}<br />
}<br />
<br />
private bool ValidateOmniWinRoot(string rootToValidate)<br />
{ <br />
bool tableInfoBestaat = false;<br />
bool dataTabellen = true;<br />
bool dataTabellenTussen = false;<br />
string tabelDir = "",tabelNaam = "";<br />
string connectionString;<br />
string sqlQuery = "SELECT ctableid, flocation, cdescr, caliasname FROM tableinfo WHERE (NOT (flocation = '')AND NOT (ctablename = ''))";<br />
tableInfoBestaat = File.Exists(rootToValidate + "tableinfo.dbf");<br />
<br />
if (tableInfoBestaat)<br />
{<br />
connectionString = "Provider=VFPOLEDB.1;" + "Data Source=" + rootToValidate + "tableinfo.dbf";<br />
using (OleDbConnection ConnTableInfo = new OleDbConnection(connectionString))<br />
{<br />
try<br />
{<br />
ConnTableInfo.Open();<br />
OleDbCommand command = new OleDbCommand(sqlQuery);<br />
command.Connection = ConnTableInfo;<br />
OleDbDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);<br />
while (reader.Read())<br />
{<br />
tabelDir = reader.GetString(1).Trim();<br />
tabelNaam = reader.GetString(3).Trim();<br />
dataTabellenTussen = File.Exists(rootToValidate + tabelDir + "\\" + tabelNaam + ".dbf");<br />
if (dataTabellenTussen == false)<br />
{<br />
dataTabellen = false;<br />
logBox.AppendText(tabelNaam + " not found!" + Environment.NewLine);<br />
}<br />
<br />
}<br />
ConnTableInfo.Close();<br />
}<br />
catch (Exception ex)<br />
{<br />
MessageBox.Show(ex.Message + Environment.NewLine + "While opening tableinfo table an error occured." + Environment.NewLine + "Make sure OmniWin is not running.");<br />
}<br />
<br />
}
<br />
}<br />
else<br />
{<br />
return false;<br />
}<br />
if (dataTabellen)<br />
{<br />
logBox.AppendText("All needed Omniwin tables are found." + Environment.NewLine);<br />
if (File.Exists(rootToValidate + "1816.txt"))<br />
{<br />
logBox.AppendText("Version file: 1816.txt" + Environment.NewLine);<br />
}<br />
else<br />
{<br />
logBox.AppendText("Versie bestand is niet aanwezig. \r\n");<br />
}<br />
toolsToolStripMenuItem.Enabled = true;<br />
return true;<br />
}<br />
else<br />
{<br />
toolsToolStripMenuItem.Enabled = false;<br />
return false;<br />
}<br />
}<br />
<br />
<br />
<br />
private void analysisIds(object sender, EventArgs e)<br />
{ <br />
<br />
string sqlQueryTableInfo ="SELECT ctableid, flocation, ctablename, caliasname, cidfield, cidindex, cdatabase FROM tableinfo WHERE (cdatabase = 'dpatient') AND (flocation = 'data') AND (cidfield <> '') ORDER BY caliasname";<br />
string connectionStringTableInfo = "Provider=VFPOLEDB.1;" + "Data Source=" + omniwinRoot + "\\tableinfo.dbf";<br />
<br />
OleDbCommand commandTableInfo = new OleDbCommand(sqlQueryTableInfo);<br />
ActiveTable tableToAnalyze = new ActiveTable();<br />
listOfDoubleIndexTables = new BindingList<ActiveTable>();<br />
<br />
using (OleDbConnection ConnTableInfo = new OleDbConnection(connectionStringTableInfo))<br />
{<br />
try<br />
{<br />
ConnTableInfo.Open();<br />
commandTableInfo.Connection = ConnTableInfo;<br />
OleDbDataReader readerTableInfo = commandTableInfo.ExecuteReader(CommandBehavior.CloseConnection);<br />
while (readerTableInfo.Read())<br />
{<br />
tableToAnalyze = new ActiveTable();<br />
tableToAnalyze.omniwinRoot = omniwinRoot;<br />
tableToAnalyze.table = readerTableInfo.GetString(3).Trim();<br />
tableToAnalyze.tableKey = readerTableInfo.GetString(4).Trim();<br />
tableToAnalyze.CheckForDoubleKey();<br />
tableToAnalyze.diff = tableToAnalyze.tableRecords - tableToAnalyze.tableUniqueRecords;<br />
<br />
if (tableToAnalyze.diff > 0)<br />
{<br />
try<br />
{<br />
listOfDoubleIndexTables.Add(tableToAnalyze);<br />
listOfDoubleIndexTables.ResetBindings();<br />
<br />
}<br />
catch (Exception dataGridErrorDoubleKey)<br />
{<br />
logBox.AppendText("Error adding table to list / datagrid: " + Environment.NewLine + dataGridErrorDoubleKey);<br />
<br />
}<br />
}<br />
<br />
}<br />
ConnTableInfo.Close();<br />
<br />
}<br />
catch (Exception ex)<br />
{<br />
MessageBox.Show(ex.Message + Environment.NewLine + "While opening tableinfo table an error occured." + Environment.NewLine + "Make sure OmniWin is not running.");<br />
}<br />
<br />
}
<br />
logBox.AppendText("***********************************" + Environment.NewLine + "Numbers of tables witch double index key(s) to repair: " + listOfDoubleIndexTables.Count + Environment.NewLine);<br />
foreach (ActiveTable tableToAnalyzeInList in listOfDoubleIndexTables )<br />
{<br />
logBox.AppendText(tableToAnalyzeInList.table + " has " + tableToAnalyzeInList.diff + " double key(s)." + Environment.NewLine);<br />
}<br />
sqlQueryTableInfo = "SELECT ctableid, flocation, ctablename, caliasname, cidfield, cidindex, cdatabase FROM tableinfo WHERE (cdatabase = 'dpatient') AND (flocation = 'data') AND (cidfield <> '') AND (mstructure LIKE '%NVERSIONID%') ORDER BY caliasname";<br />
using (OleDbConnection ConnTableInfo = new OleDbConnection(connectionStringTableInfo))<br />
{<br />
try<br />
{<br />
ConnTableInfo.Open();<br />
commandTableInfo.Connection = ConnTableInfo;<br />
OleDbDataReader readerTableInfo = commandTableInfo.ExecuteReader(CommandBehavior.CloseConnection);<br />
while (readerTableInfo.Read())<br />
{<br />
tableToAnalyze = new ActiveTable();<br />
tableToAnalyze.omniwinRoot = omniwinRoot;<br />
tableToAnalyze.table = readerTableInfo.GetString(3).Trim();<br />
tableToAnalyze.tableKey = readerTableInfo.GetString(4).Trim();<br />
tableToAnalyze.CheckNversionid();<br />
if (tableToAnalyze.noNversion.Count > 0)<br />
{<br />
try<br />
{<br />
listOfNoNversionid.Add(tableToAnalyze);<br />
listOfDoubleIndexTables.ResetBindings();<br />
<br />
}<br />
catch (Exception dataGridErrorVersionid)<br />
{<br />
logBox.AppendText("datagriderror bij version id:" + Environment.NewLine + dataGridErrorVersionid.Message);<br />
}<br />
}<br />
<br />
}<br />
ConnTableInfo.Close();<br />
<br />
}<br />
catch (Exception ex)<br />
{<br />
MessageBox.Show(ex.Message + Environment.NewLine + "While opening tableinfo table an error occured." + Environment.NewLine + "Make sure OmniWin is not running.");<br />
<br />
}<br />
}
<br />
logBox.AppendText("***********************************" + Environment.NewLine + "Numbers of tables with versionid problems to repair: " + listOfNoNversionid.Count + Environment.NewLine);<br />
foreach (ActiveTable tableToAnalyzeInList in listOfNoNversionid)<br />
{<br />
logBox.AppendText(tableToAnalyzeInList.table + " has nversionid problem(s)." + Environment.NewLine);<br />
}<br />
<br />
}<br />
<br />
}<br />
<br />
public class ActiveTable<br />
{<br />
public string omniwinRoot;<br />
public string table;<br />
public string tableKey;<br />
public int tableRecords;<br />
public int tableUniqueRecords;<br />
public int diff;<br />
public Queue<string> doubleKey = new Queue<string>();<br />
public Queue<string> noNversion = new Queue<string>();<br />
public void CheckForDoubleKey()
{<br />
string connectionStringDpatient = "Provider=VFPOLEDB.1;" + "Data Source=" + omniwinRoot + "\\data\\" + table + ".dbf";<br />
string queryNumberRows = "SELECT " + tableKey + " FROM " + table + " WHERE (nversionid = 0) AND (ldeleted = 0) " + "ORDER BY " + tableKey;<br />
string queryDistinctRows = "SELECT DISTINCT " + tableKey + " FROM " + table + " WHERE (nversionid = 0) AND (ldeleted = 0) " + " ORDER BY " + tableKey;<br />
string queryGetNotDistinctId = "SELECT " + tableKey + ",nversionid, ldeleted, count(*) AS count FROM " + table + " GROUP BY " + tableKey + ",nversionid, ldeleted HAVING count > 1 AND nversionid = 0 AND (ldeleted = 0) ";<br />
<br />
try<br />
{<br />
OleDbConnection ConnDpatient = new OleDbConnection(connectionStringDpatient);<br />
OleDbCommand command = new OleDbCommand(queryNumberRows);<br />
command.Connection = ConnDpatient;<br />
ConnDpatient.Open();<br />
<br />
tableRecords = command.ExecuteNonQuery();<br />
command.CommandText = queryDistinctRows;<br />
tableUniqueRecords = command.ExecuteNonQuery();<br />
if (tableRecords - tableUniqueRecords > 0)<br />
{<br />
command.CommandText = queryGetNotDistinctId;<br />
try<br />
{<br />
OleDbDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);<br />
while (reader.Read())<br />
{<br />
doubleKey.Enqueue(reader.GetString(0).Trim());<br />
}<br />
<br />
}<br />
catch (Exception getDoubleIds)<br />
{<br />
MessageBox.Show(getDoubleIds.Message + Environment.NewLine + "Error while getting double key values.");<br />
}<br />
<br />
}<br />
ConnDpatient.Close();<br />
}<br />
catch (Exception analyzeTableError)<br />
{<br />
if (analyzeTableError.Message != "SQL: Column 'NVERSIONID' is not found." && analyzeTableError.Message != "SQL: Column 'COPNAMEID' is not found.")<br />
{<br />
MessageBox.Show(analyzeTableError.Message + Environment.NewLine + "Error while analyzing table " + table);<br />
}<br />
}<br />
<br />
}<br />
<br />
public void CheckNversionid()<br />
{<br />
string connectionStringDpatient = "Provider=VFPOLEDB.1;" + "Data Source=" + omniwinRoot + "\\data\\" + table + ".dbf";<br />
string queryAll = "SELECT " + tableKey + ", nversionid FROM " + table + " WHERE (ldeleted = 0) ORDER BY " + tableKey + ", nversionid";<br />
OleDbConnection ConnDpatient = new OleDbConnection(connectionStringDpatient);<br />
string previousIndex = "";<br />
<br />
try<br />
{<br />
OleDbCommand command = new OleDbCommand(queryAll);<br />
OleDbCommand commandOnId = new OleDbCommand();<br />
command.Connection = ConnDpatient;<br />
ConnDpatient.Open();<br />
<br />
OleDbDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);<br />
while (reader.Read())<br />
{<br />
if (reader.GetString(0).Trim() != previousIndex)<br />
{<br />
if (reader.GetValue(1).ToString().Trim() != "0")<br />
{<br />
noNversion.Enqueue(reader.GetString(0).Trim());<br />
<br />
}<br />
}<br />
previousIndex = reader.GetString(0).Trim();<br />
}
<br />
ConnDpatient.Close();<br />
}<br />
catch (Exception analyzeTableError)<br />
{ <br />
if (analyzeTableError.Message != "SQL: Column 'NVERSIONID' is not found." && analyzeTableError.Message != "SQL: Column 'COPNAMEID' is not found.")<br />
{<br />
MessageBox.Show(analyzeTableError.Message + Environment.NewLine + "Error while checking the nversionid's of table: " + table);<br />
<br />
}<br />
}<br />
<br />
}<br />
}<br />
<br />
}
|
|
|
|
|
OK, this was entirely too much code.
If I can follow this pile of spaghetti, you're creating a BindingList to hold something, but you never tell the compiler what. BindingList wants to know what kind of type it's holding, like this:
BindingList listOfDoubleIndexTables = new BindingList<ActiveTable>();
Also, you may want to rewrite the code in the ActiveTable class to expose public data as properties and not public fields.
-- modified at 19:20 Tuesday 29th May, 2007
|
|
|
|
|
that's already in there:
<br />
public partial class FormAlgemeen : Form<br />
{<br />
<br />
BindingList<ActiveTable> listOfDoubleIndexTables = new BindingList<ActiveTable>();<br />
Good point about my modifiers though, i'll rewrite and add some accessor methods later.
|
|
|
|
|
Whooops! Go look at my previous post again.
|
|
|
|
|
wel, defining properties did the trick.
Thanks a lot for your time, and patience
|
|
|
|
|
Hello everyone,
I have a ComboBox with some options to select. I don't wish the user to be able to Edit the options in the ComboBox. Just simply select the options. I have checked the Windows Property of the ComboBox and I cannot find any options to disable the Text Edit of the ComboBox.
I was wondering if anyone can tell me how I can stop the user from editing the options in the ComboBox.
Thank you very much and have a great day.
Khoramdin
|
|
|
|
|
Either use a ListBox or check out the DropDown[^] property of the ComboBox.
|
|
|
|
|
Set the DropDownStyle property to DropDownList
|
|
|
|
|
I have a device that is connected to our local network that I need to send data to it to preform a action. Can someone give me some advice on how to acomplish this?
|
|
|
|
|
That depends entirely on the requirements of the device. Ethernet doesn't define a transport protocol or any kind of communication protocol, which is what you need to know to talk to the device, like TCP/IP. Ethernet merely defines the standards for the physical and data link layers of the OSI model. You need to know what the device defines for everything above these two layers to talk to it.
The first place to start is the manual for the device.
|
|
|
|
|
Device is a little vague. Does this device have any interfaces you can write code to hook into it? I think we need a little more info here.
I wrote some old delphi code that used an activeX a while back to work with a device on the network, but I am pretty sure it won't help you in this case.
Ben
|
|
|
|
|
Sorry about being vague. This is for a thermal printer. They do have some docs at http://bocasystems.com/ethernet.html which more or less talk about connecting the printer to a network, printer drivers at http://bocasystems.com/driver.html which allowed me to hook up to USB to print from word, and then the manual at http://bocasystems.com/mini_manual.doc.
I dont expect you to read all of this info and help me out, but they do provide the information. I have a older version of this printer which was connected via a serial port and to make it print and to format the text I had to send code like below to the printer. So I have to be able to send this same code to my new printer.
<rc15,860><hw1,1><f2>Ticket #
<rc51,860><hw1,1><f2>SECTION
<rc74,860><hw2,2><f2>
<rc68,843><lt2><bx36,225>
<rc121,860><hw1,1><f2>ROW
<rc141,860><hw2,2><f2>
<rc136,843><lt2><bx36,225>
<rc191,860><hw1,1><f2>SEAT
<rc213,860><hw2,2><f2>
<rc206,843><lt2><bx36,225>
<rc261,860><hw1,1><f2>PRICE
<rc283,860><hw2,2><f2>$
<rc276,843><lt2><bx36,225>
Does any of this help with what I am needing to try and do?
|
|
|
|
|
|
Well if I have the drivers installed onto my system it does allow me to just print from word, but the problem is I have to send it the code on how to print and not formatted like in my previous post. This tells the printer where to draw box's, where to add a barcode etc. Would that active x you wrote send data to the system like this (raw data not formatted for printing)? I think that is what is needed but I am not 100% sure.
|
|
|
|
|
If you really have a lot of need to do custom printing, there are some things to consider. First I hope your printer accepts post script. If it does, I would just create a post script file and send it to the printer. If it has its own printer language, then you will have to abide by whatever that language is. The activeX control I used was for a device that wasn't a printer. The printers I have worked with I have either sent postscript or custom printer language. I didn't check those urls you sent, but I am sure somewhere in there it would say if the printer has a postscript print driver or if it has a custom print language that it uses.
Ben
|
|
|
|
|
Sorry I just realized the code I pasted was seen as html in one of my pervious posts. If you goto http://www.marketingwebforce.com/tix-text.txt you will see the text that I used to send to the printer so yes it has its own printer language. If the active x is not for a printer, if its just able to send data to a set IP Address then it seems like it would also work. I am trying to get something figured out in the next couple days with this and if the price is right I would even purchase the ActiveX from you if it did the job for me. The only thing that is a little different is per each computer it is used on I would like a setting to say where to send the data (USB or TCP/IP & what IP Address) so that way this same program could be used no matter when I am.
|
|
|
|
|
Just to update my last post.. this printer does have a print driver which I have installed but then I can not code to format the print correctly which is why I need to send it the printer language.
|
|
|
|
|
The activeX I used was for a custom scanner, so it would not work with this printer. I think your only option is to install the print driver where ever you think you will run the program and just the printer drive to communicate to the printer. When you send that file to the printer it should understand it and print what you are asking it to print.
If you are using a usb connection you might have some copy options so you wouldn't have to use the print driver then. I would try hooking up to the usb see how the device maps on the computer. Certainly if it shows up as a drive you could copy to it, most likely it will just try to load the printer driver for it.
Ben
|
|
|
|
|
Is there a way to get the users MyDocuments location with C#? I have a folder browser dialog that I would like to default to that location when the user opens it. Thank you
|
|
|
|
|
This should do it:
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
Ben
|
|
|
|
|
|
Thank you very much for the help!;)
|
|
|
|
|
Can anyone shed any light on the reason why when I execute the following code to set a time for 1 minute, i.e. 60000 milliseconds it takes around 3 minutes for the timer to 'tick'.
<br />
public void setCheckInterval(int i_checkInterval)<br />
{<br />
switch (i_checkInterval)<br />
{<br />
case 0:<br />
_i_checkInterval = -1; break;<br />
case 1:<br />
_i_checkInterval = 60000; break;<br />
case 2:<br />
_i_checkInterval = 60000 * 3; break;<br />
case 3:<br />
_i_checkInterval = 60000 * 5; break;<br />
case 4:<br />
_i_checkInterval = 60000 * 10; break;<br />
case 5:<br />
_i_checkInterval = 60000 * 15; break;<br />
case 6:<br />
_i_checkInterval = 60000 * 20; break;<br />
case 7:<br />
_i_checkInterval = 60000 * 25; break;<br />
case 8:<br />
_i_checkInterval = 60000 * 30; break;<br />
case 9:<br />
_i_checkInterval = 60000 * 60; break;<br />
}<br />
...<br />
Freedom is the right to say that 2+2=5 if this is so everything else will follow.
|
|
|
|
|