Notebook C# |
Alex van Buitenen |
This is not a tutorial nor a complete quick reference.
This document is just my personal memo block for C#.
Retrieve logged on windows
username.
Saving User settings.
Application Configuration settings.
Access Configuration information.
Working with Threads.
Sort issues.
Exceptions.
Cursor.
DataGridView.
ComboBox.
Outlook automation.
Word automation.
Walking thru a directory.
Drag and Drop.
Checking whether the user pressed Ctrl/Shift.
Using
XmlSerializer to serialize a class to/from XML.
Color FromKnowColor.
CheckedListBox does not give any cues that it is selected.
Handling key press.
Object browser does not display help for my dll.
Defining an icon for a user control to
be shown by the Toolbox.
Setting an Approle.
Retrieving Type from string.
Serialize Type to Xml.
AssemblyVersion vs
AssemblyFileVersion.
Adding a local report in WinForms.
DateTime.ToString formatting
using System.Security.Principal;
WindowsIdentity windowsIdentity =
WindowsIdentity.GetCurrent();
string userName = windowsIdentity.Name;
Add a settings file to the project (or rightclick project and choose
Settings).
Add a property <PropertyName> to the settings.
Set Scope to User.
Use:
Properties.Settings.Default.<PropertyName>
and
Properties.Settings.Default.Save();
In the Visual Studio 2005 Help, this in not described.
They only explain using My from Visual Basic, and not a C# variant.
See Using
Settings in C# in MSDN Online for the missing help.
Where are the settings stored?
Somewhere in user.config in
Documents and Settings\<username>\Local Settings\Application Data\
see also
Application Settings Architecture in MSDN online.
The settings allow User and Application settings.
Application settings cannot be modified by the user.
Both User and Application settings have their initial values in the app.config
file.
In app.config:
<?xml
version="1.0"
encoding="utf-8"
?>
<configuration>
<connectionstrings>
<add name="PPI"
connectionString="Server=(local)\SQLEXPRESS2008;Database=LCT_ONT;Integrated
Security=true"/>
</connectionstrings>
<appSettings>
<add
key="ServerName"
value="rdv10t"
/>
</appSettings>
</configuration>
In C# file:
string ServerName =
System.Configuration.ConfigurationSettings.AppSettings["ServerName"];
ConnectionStringSettings cs = ConfigurationManager.ConnectionStrings["PPI"];
See MSDN Help, <appsettings>.
To work with configuration files for client applications, use ConfigurationManager (or WebConfigurationManager for ASP).
sample:
ApplicationSettings.Properties.Settings settings = new
ApplicationSettings.Properties.Settings();
Configuration config = ConfigurationManager.OpenExeConfiguration(Application.ExecutablePath);
config.AppSettings.Settings.Add("Key2", "Value2");
config.Save(); //saves the config file in the in the bin/{Debug|Release}
directory
definition of MyThreadWithState.CS |
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace ThreadDemo { // Delegate that defines the signature for the callback method. public delegate void MyThreadCallback(int lineCount); public class MyThreadWithState { // State information used in the task. private string _message; private int _value; // Delegate used to execute the callback method when the // task is complete. private MyThreadCallback callback; // The constructor obtains the state information and the // callback delegate. public MyThreadWithState(string text, int number, MyThreadCallback callbackDelegate) { _message = text; _value = number; callback = callbackDelegate; } // The thread procedure performs the task, such as // formatting and printing a document, and then invokes // the callback delegate with the number of lines printed. public void ThreadProc(object param) { Console.WriteLine("Param = " + param as string); for (int i=0;i<3;i++) { Console.WriteLine("executing ThreadMethod(" + i.ToString() + ")"); Thread.Sleep(3000); } Console.WriteLine(_message, _value); if (callback != null) callback(1); } } } |
usage of MyThreadWithState |
private void StartThread() { MyThreadWithState myThreadWithState = new MyThreadWithState( "This report displays the number {0}.", 42, new MyThreadCallback(ResultCallback)); Thread InstanceCaller = new Thread( new ParameterizedThreadStart(myThreadWithState.ThreadProc)); // Start the thread. InstanceCaller.Start("Test"); } // The callback method must match the signature of the callback delegate. public static void ResultCallback(int lineCount) { Console.WriteLine("Result of callback is: {0}.", lineCount); } |
To be able to save a Graphics object to file, create the Graphics from a BitMap, and then use BitMap.Save to save to file.
private Graphics _graphics;
private Bitmap _bitmap;
_bitmap = new Bitmap(200, 200);
_graphics = Graphics.FromImage(_bitmap);
_bitMap.Save("D:\\temp\\test.bmp");
Set Autoscroll to true.
Draw like:
e.Graphics.DrawEllipse(Brushes.Red,10+AutoScrollPosition.X,10+AutoScrollPosition.Y,100,100)
OR
Matrix mx=new Matrix(1, 0, 0, 1, AutoScrollPosition.X, AutoScrollPosition.Y);
e.Graphics.Transform=mx;
e.Graphics.DrawEllipse(Brushes.Red,10,10,100,100)
For explanation about Matrix, see:
GDI+ Graphics in Visual Basic 2005 .NET - Part 5
Matrix Transformation of Images in C#, using .NET GDI+
see also Understanding Windows Forms AutoScroll
private SortedList<string,MyType> _constraints = new SortedList<string,MyType>(StringComparer.OrdinalIgnoreCase);
For guidelines on exceptions, see Catching and Throwing Standard Exception Types on MSDN.
Do not throw System.Exception or System.SystemException.
Do not catch System.Exception or System.SystemException in framework code,
unless you intend to re-throw.
Avoid catching System.Exception or System.SystemException, except in top-level
exception handlers.
If you are designing an application that needs to create its own exceptions, you are advised to derive custom exceptions from the Exception class.
When creating your own exceptions, it is good coding practice to end the
class name of the user-defined exception with the word "Exception."
It is also good practice to implement the three recommended common constructors,
as shown in the following example.
using System;
public class EmployeeListNotFoundException: Exception
{
public EmployeeListNotFoundException() { }
public EmployeeListNotFoundException(string message) :
base(message) { }
public EmployeeListNotFoundException(string message,
Exception inner) : base(message, inner) { }
}
// Memorize the current cursor
Cursor savedCursor = Cursor.Current;
// Set the wait cursor
Cursor.Current = Cursors.WaitCursor;
// Do something time consuming...
//
// Restore the memorized cursor
Cursor.Current = savedCursor;
// Get the index of the current row
DataGridViewRow currentRow = dataGridView1.CurrentRow;
int index = dataGridView1.Rows.IndexOf(currentRow);
// Here something happens disturbs the current row of the DataGridView, like
a refresh of the contents of the DataGridView.
//...
// Now restore the previously selected row
if (index > 0)
{
if (index < dataGridView1.Count)
{
DataGridViewRow selectedRow = dataGridView1.Rows[index];
dataGridViewPotentielePublicatiegroepen.CurrentCell = selectedRow.Cells[0];
}
}
Problem:
DataGridView does not contain a line with "*" that indicates you can add a
new row.
Cause:
List<ParameterInfo> coll = new List<ParameterInfo>()
//dataGridView1.DataSource = coll;
//Oeps, forgot that Visual Studio has added a BindingsSource as a level of
indirection:
//this.dataGridView1.DataSource = this.parameterInfoBindingSource;
//So setting the DataSource should be done in the BindingSource:
parameterInfoBindingSource.DataSource = coll;
Solved by setting checking DataSource's Count, and setting Datasource to null if neccessary.
Want to use a DataGridView to select one line. (Set MultiSelect of the
DataGridView to False)
Replace DATABOUNDITEM by the type of the databound item.
/// <summary>
/// Gets the DATABOUNDITEMRecord which is selected in the DataGridView.
/// </summary>
/// <returns>the selected DATABOUNDITEM record.</returns>
/// <remarks>
/// Picks the selected row, otherwise the row of the selected cell.
/// Assuming the DataGridView only allows single selection.
/// </remarks>
private DATABOUNDITEM GetDATABOUNDITEMRecord()
{
DataGridViewRow dataGridViewRow = null;
DATABOUNDITEM myItem = null;
if (dataGridViewDATABOUNDITEM.SelectedRows.Count > 0)
{
// Get selected row using SelectedRows.
dataGridViewRow =
dataGridViewDATABOUNDITEM.SelectedRows[0];
if (dataGridViewRow != null)
{
myItem =
dataGridViewRow.DataBoundItem as DATABOUNDITEM;
}
}
else
{
// Get selected row using SelectedCells
int rowIndex;
if
(dataGridViewDATABOUNDITEM.SelectedCells.Count > 0)
{
rowIndex =
dataGridViewDATABOUNDITEM.SelectedCells[0].RowIndex;
dataGridViewRow =
dataGridViewDATABOUNDITEM.Rows[rowIndex];
if (dataGridViewRow
!= null)
{
myItem = dataGridViewRow.DataBoundItem as Correspondentie;
}
}
}
return myItem ;
}
Got error using this code:
// Put operator in a ComboBox
// Define the operators
const string GT = ">";
const string GTE = ">=";
const string EQ = "=";
const string LTE = "<=";
const string LT = "<";
// Put them in an array
string[] _operatorItems = new string[5] { GT,GTE,EQ,LTE,LT };
// Create the Combobox
ComboBox comboBox = new ComboBox();
// Only allow items from the List
comboBox.DropDownStyle = ComboBoxStyle.DropDownList;
// Databind it
comboBox.DataSource = _operatorItems;
comboBox.SelectedIndex = 0;
InvalidArgument=Value of '0' is not valid for 'SelectedIndex'.
Parameter name: SelectedIndex
Tried setting SelectedItem, SelectedValue.
Doesn't work.
Items is not filled (because Databinding was used)
//Don't use Databinding, just fill Items:
foreach (string str in _operatorItems)
{
comboBox.Items.Add(str);
}
Voeg een reference toe : Microsoft.Office.Interop.Outlook
(Voor VS2010 is dit: C:\Program Files\Microsoft Visual Studio 10.0\Visual Studio
Tools for Office\PIA\Office14\Microsoft.Office.Interop.Outlook.dll )
MSDN Help:
http://msdn.microsoft.com/en-us/library/microsoft.office.interop.outlook.aspx
How
to: Obtain and Log On to an Instance of Outlook
C# Outlook 2007 COM interop application does not exit!
Microsoft .NET Development for Microsoft Office (Part 2).
See example:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Diagnostics; using System.Windows.Forms; using System.Reflection; using System.Threading; using System.Runtime.InteropServices; //Uit C:\Program Files\Microsoft Visual Studio 10.0\Visual Studio Tools for Office\PIA\Office14\Microsoft.Office.Interop.Outlook.dll using Outlook = Microsoft.Office.Interop.Outlook; namespace TestOutlook { public class OutlookWorker { public bool SendEmail( string subject, string body, string to, string from,string fromName, string [] attachments) { bool ok = false; bool startedNewOutlook = false; //The MAPI profile name, as a String (string in C#), to use for the session. //Specify an empty string to use the default profile for the current session. string mapiProfile = ""; try { Outlook.Application oApp = null; Outlook.NameSpace ns = null; // Check if there is an Outlook process running. if (Process.GetProcessesByName("OUTLOOK").Count() > 0) { // If so, use the GetActiveObject method to obtain the process and cast it to an Application object. oApp = Marshal.GetActiveObject("Outlook.Application") as Outlook.Application; } else { // If not, create a new instance of Outlook and log on to the default profile. oApp = new Outlook.Application(); ns = oApp.GetNamespace("MAPI"); ns.Logon(mapiProfile, "", Missing.Value, Missing.Value); startedNewOutlook = true; } Outlook.MailItem oMsg = (Outlook.MailItem)oApp.CreateItem(Outlook.OlItemType.olMailItem); oMsg.HTMLBody = body; int iPosition = (int)oMsg.HTMLBody.Length + 1; int iAttachType = (int)Outlook.OlAttachmentType.olByValue; if (attachments != null) { foreach (string attachment in attachments) { String sDisplayName = Path.GetFileName(attachment); Outlook.Attachment oAttach = oMsg.Attachments.Add( attachment, iAttachType, iPosition, //This parameter applies only to e-mail messages using //Microsoft Outlook Rich Text format: //it is the position where the attachment should be placed //within the body text of the message. //A value of 1 for the Position parameter specifies that //the attachment should be positioned at the beginning of //the message body. A value 'n' greater than the number //of characters in the body of the e-mail item specifies //that the attachment should be placed at the end. //A value of 0 makes the attachment hidden. sDisplayName); } } oMsg.Subject = subject; Outlook.Recipients oRecips = (Outlook.Recipients)oMsg.Recipients; Outlook.Recipient oRecip = (Outlook.Recipient)oRecips.Add(to); oRecip.Resolve(); oMsg.Send(); ok = true; if (startedNewOutlook) { ns.Logoff(); ns = null; ((Outlook._Application)oApp).Quit(); } // See MSDN, Microsoft .NET Development for Microsoft Office (Part 2) // http://msdn.microsoft.com/en-us/library/aa679807%28office.11%29.aspx#officeinteroperabilitych2_part2_rco // //In the previous code we reference the Outlook.Application object and the Outlook.MailItem object. //Both are COM objects that are implicitly created when we use the property. //We don't keep any managed references to these objects ourselves, //but that in itself confuses many developers. //The CLR will have wrapped these objects for us, and because we don't have explicit //references to them, we have no way of explicitly releasing them. //Instead, we rely on the CLR to release them on our behalf. // //(If you look at the task manager, OUTLOOK.EXE only stops after the calling program has ended.) //There are a couple of ways of making our cleanup operations more deterministic—and more aggressive. //method 1: Simple Garbage Collection //perform simple cleanup, by nulling the references and forcing garbage collection: oRecip = null; oRecips = null; oMsg = null; oApp = null; GC.Collect(); GC.WaitForPendingFinalizers(); //it takes a moment, but OUTLOOK.EXE stops (when a new instance was started). //method2: Marshal.ReleaseComObject //As an alternative, you can use Marshal.ReleaseComObject. //However, note that you are very unlikely to ever need to use this method. // (So we don't do that here, but leave the comment for documentation purposes) // //Marshal.ReleaseComObject(oMsg); //while (Marshal.ReleaseComObject(oApp) > 0) {} } catch (System.Runtime.InteropServices.COMException ex) { MessageBox.Show(ex.Message, "Error while sending mail", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (System.IO.FileNotFoundException ex) { MessageBox.Show(ex.Message, "Error while sending mail", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (System.UnauthorizedAccessException ex) { MessageBox.Show(ex.Message, "Error while sending mail", MessageBoxButtons.OK, MessageBoxIcon.Error); } return ok; } } }
Voeg een reference toe : Microsoft.Office.Interop.Word
(Voor VS2010 is dit: C:\Program Files\Microsoft Visual Studio 10.0\Visual Studio
Tools for Office\PIA\Office14\Microsoft.Office.Interop.Word.dll)
MSDN Help:
Word primary interop assembly reference.
using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Windows.Forms; using System.Threading; // Uit C:\Program Files\Microsoft Visual Studio 10.0\Visual Studio Tools for Office\PIA\Office14\Microsoft.Office.Interop.Word.dll using Word = Microsoft.Office.Interop.Word; namespace TestWord { /// <summary> /// /// </summary> /// <remarks> /// See http://msdn.microsoft.com/en-us/library/ms228772.aspx /// and http://social.msdn.microsoft.com/Forums/en-CA/vsto/thread/1dab0874-bba5-481a-9986-a11a66efa0a0 /// </remarks> public class WordTemplateWorker { private Word.Application _wordApplication = null; private Word.Document _wordDocument = null; /// <summary> /// -Accepts a Word template (with extension .dotx) as parameter /// -fills in the template fields. /// -Saves the filled template as .docx file. /// </summary> /// <param name="templateName"></param> /// <returns></returns> public bool ProcessTemplate( string templateName, string destinationFilename, Dictionary<string,string> keyValues) { bool success = false; //Start Word _wordApplication = new Word.Application(); if (_wordApplication != null) { _wordApplication.Visible = false; //Add template to document success = AddingTemplateToDocument(templateName); if (!success) MessageBox.Show("AddingTemplateToDocument failed"); //fill template if (success) success = FillingTemplate(keyValues); if (!success) MessageBox.Show("FillingTemplate failed"); //save filled template if (success) { // save as docx. _wordDocument.SaveAs2(destinationFilename as object, Word.WdSaveFormat.wdFormatDocumentDefault); } if (_wordDocument != null) (_wordDocument as Word._Document).Close(); (_wordApplication as Word._Application).Quit(); } return success; } private bool AddingTemplateToDocument(string templateName) { bool success = false; int retryCounter = 0; do { try { retryCounter++; _wordDocument = _wordApplication.Documents.Add(templateName as object); //this worked fine in the debugger with breakpoints, but wordDocument was null without the breakpoints. // keep trying for a while. // See also: remarks of this class. if (_wordDocument != null) success = true; } catch (System.Runtime.InteropServices.COMException ex) { } } while ((success == false) && (retryCounter < 10)); return success; } private bool FillingTemplate(Dictionary<string,string> keyValues) { int retryCounter = 0; bool success = false; do { try { retryCounter++; Console.WriteLine("retryCounter = " + retryCounter.ToString()); FillTemplate(keyValues); success = true; } catch (System.Runtime.InteropServices.COMException ex) { Thread.Sleep(1000); // Didn't work properly, give Word some time to initialize. //if ((UInt32)ex.ErrorCode == 0x80010001) // MessageBox.Show("ErrorCode0x80010001"); //MessageBox.Show(ex.Message + " "+ ex.ErrorCode.ToString()); } } while ((success == false) && (retryCounter < 10)); return success; } private void FillTemplate(Dictionary<string,string> keyValues) { //De aanroep van _wordDocument.Fields kan een COMException opleveren. // "Aanroep geweigerd door aangeroepene. (Uitzondering van HRESULT: 0x80010001 // RPC_E_CALL_REJECTED))" foreach (Word.Field wordField in _wordDocument.Fields) { if (wordField.Type == Word.WdFieldType.wdFieldMergeField) { Word.Range range = wordField.Code; string fieldname = GetFieldnameFromRange(range).ToUpper(); if (keyValues.ContainsKey(fieldname)) { string fieldValue = keyValues[fieldname]; if (fieldValue != null) VulField(wordField, _wordApplication, fieldValue); } } } } private void VulField( Word.Field wordField, Word.Application wordApplication, object value) { wordField.Select(); wordApplication.Selection.TypeText(value.ToString()); } private string GetFieldnameFromRange(Word.Range range) { // range heeft onderstaand formaat // MERGEFIELD voornaam \* MERGEFORMAT string[] strArray = range.Text.Trim().Split(' '); return strArray[2]; } public bool Print(string fileName) { bool ret = false; Word.Application wordApplication = null; Word.Document wordDocument = null; try { wordApplication = new Word.Application(); wordDocument = new Word.Document(); wordApplication.Visible = false; object documentName = fileName; wordDocument = wordApplication.Documents.Add(documentName); wordDocument.PrintOut(); ret = true; } catch (Exception ex) { MessageBox.Show( "Het printen van het Word document " + fileName + "is mislukt\n\r" + (ex.InnerException == null ? ex.Message : ex.InnerException.Message), "Fout tijdens het printen.", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { if (wordDocument != null) (wordDocument as Word.Document).Close(); if (wordApplication != null) (wordApplication as Word.Application).Quit(); } return ret; } } }
using System.IO;
DirectoryInfo directoryInfo = new DirectoryInfo(templateLokatie);
IEnumerable<FileInfo> ifileInfos = directoryInfo.EnumerateFiles("*.dotx");
//Create a List because ListBox doesn't accept a IEnumerable
List<FileInfo> fileInfos = new List<FileInfo>();
foreach (FileInfo fi in ifileInfos)
{
fileInfos.Add(fi);
}
For extensive examples.see MSDN
private void MyControl_KeyDown(object sender, KeyEventArgs e)
{
_shiftPressed = e.Shift;
_ctrlPressed = e.Control;
}
using System.Xml;
using System.Xml.Serialization;
[XmlInclude(typeof(MyClass2))] //<- Use XmlInclude to include types not known at
compile time (MyClass2 not included in this sample)
class MyClass
{
private string _name;
private string _sample;
public static MyClass ReadFromXml(string fileName)
{
StreamReader streamReader = new StreamReader(fileName);
XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyClass));
object obj = xmlSerializer.Deserialize(streamReader);
MyClass myClass = obj as MyClass;
return myclass;
}
public void WriteToXml(string fileName)
{
XmlWriterSettings ws = new XmlWriterSettings();
ws.NewLineHandling = NewLineHandling.Entitize;
ws.Indent = true;
XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyClass));
using (XmlWriter xmlWriter = XmlWriter.Create(fileName, ws))
{
xmlSerializer.Serialize(xmlWriter, this);
}
}
[XmlAttribute] //<- use XmlAttribute to write as attribute, instead of node
public string Name
{
get { return _name; }
set { _name = value; }
}
[XmlIgnore] //<- use XmlIgnore to prevent writing
to xml.
public string Sample
{
get { return _sample; }
set { _sample = value; }
}
}
Controls have a Color such as "Window" for BackColor in the properties window of
Visual Studio.
However, BackColor is a Color, ans class Color doen not have "Window".
There is a class "KnownColor" that has "Window", but you cannot cast from
KnowColor to Color.
Use Color.FromKnownColor(KnownColor.Window);
Solved by giving background color when Entering/leaving the control:
private void checkedListBox_UDFs_Enter(object sender, EventArgs e)
{
checkedListBox_UDFs.BackColor = Color.FromKnownColor(KnownColor.ControlLight);
}
private void checkedListBox_UDFs_Leave(object sender, EventArgs e)
{
checkedListBox_UDFs.BackColor = Color.FromKnownColor(KnownColor.Window);
}
event | key info parameter |
KeyDown | KeyEventArgs |
KeyPress | KeyPressEventArgs |
KeyUp | KeyEventArgs |
KeyEventArgs has more info than KeyPressEventArgs. If you want to handle arrow keys, use KeyDown/KeyUp instead of KeyPress!
Make sure the XML documentation file (that can be generated by Visual Studio; Project
properties, Build, Xml Documentation file)
is placed next to the Dll you want to browse with object browser.
Create a bitmap (in my case 16x16, 16 color)
Set Build action to embedded resource.
Set "Copy to output directory"to "Copy if newer"
Use the ToolboxBitmap attribute:
[ToolboxBitmap("TextBoxOpenFile.bmp")]
public class TextBoxOpenFile : System.Windows.Forms.UserControl
<add key="AppRoleName" value="AppRolename" />
<add key="AppRolePassword" value="EncryptedPassword" />
Na het connecten met de database SetApprole uitvoeren:
private void SetAppRole()
{
string appRoleName = ConfigurationManager.AppSettings["AppRoleName"];
string appRolePassword = ConfigurationManager.AppSettings["AppRolePassword"];
appRolePassword = Encryption.Decrypt(appRolePassword);
// Activate AppRole
SqlCommand activateAppRole = new SqlCommand();
activateAppRole.CommandText = "sp_setapprole";
activateAppRole.CommandType = CommandType.StoredProcedure;
activateAppRole.Connection = _conn;
activateAppRole.Parameters.AddWithValue("@rolename", appRoleName);
activateAppRole.Parameters.AddWithValue("@password", appRolePassword);
int success = activateAppRole.ExecuteNonQuery();
if (success != -1)
{
throw new Exception("Activating approle '" + appRoleName +
"' has failed");
}
}
Type type = Type.GetType("System.Int32");
see AssemblyInfo.cs
The version number has four parts, as follows:
<major version>.<minor version>.<build number>.<revision>
You can specify all the values or you can accept the default build number,
revision number, or both by using an asterisk (*).
If you specify an asterisk for the build number, you cannot specify a
revision number.
default build number increments daily. The default revision number is random.
The file version is normally a string of four numbers, separated by periods, reflecting the major version number, minor version number, build number, and revision number; for example, "1.0.4309.00". If version is not in this format, a compiler warning occurs, and the results displayed in the file properties dialog are unpredictable. Wildcards are not supported.
Source: MSDN
If you are a .NET developer, Assembly and File version would be familiar to you. They can be set in the Designer UI (project Properties page, Application tab, Assembly Information button...) which basically updates appropriate attributes in AssemblyInfo.cs.
sample: dateTime.ToString("yyyyMMdd")
Custom Date and Time Format Strings