Enviar Email y Adjuntos Usando CSharp

download Enviar Email y Adjuntos Usando CSharp

of 44

description

Send Email Messages and Attachments Using C#

Transcript of Enviar Email y Adjuntos Usando CSharp

Send Email Messages and Attachments Using C#

ByScott LysleAugust 05, 2008This article describes an approach to sending email messages with or without attachments. The code required to send the message is contained within a reusable class. In addition to sending messages and messages with attachments, the class also validates the recipient email addresses using regular expression validation.

Author Rank:Total page views :125702

Total downloads :4056

Print

Post a comment

Similar Articles

Share

Email to a friend

Bookmark

Author's other articles

Download Files:EmailAttachments_CS.zip

Sponsored byBecome a SponsorSimilar ArticlesMost ReadTop RatedLatestIntroduction to Dynamic Data Web Application Model: Part IIIClient side validation using Validation Application blocksWorking with ASP.NET Validation controlsSelective Validation in ASP.Net 2.0Validation Controls within GridView control in ASP.NET 2.0More...

IntroductionThis article describes an approach to sending email messages with or without attachments. The code required to send the message is contained within a reusable class. In addition to sending messages and messages with attachments, the class also validates the recipient email addresses using regular expression validation.

Figure 1: Test Application Main Form

SMTP ConfigurationSetting up IISYour local IIS instance has to be properly configured in order to successfully send an email message through its SMTP mail server. Even if you install IIS, the SMTP mail server installation is optional and must be explicitly added to the installation. If you are not sure whether or not the SMTP mail server is installed, open up the IIS control panel and check for the installation; if it is installed you will see a reference to the Default SMTP Virtual Server in the tree view (Figure 2):

Figure 2: Default SMTP Virtual Server Installed

If the server is not installed, you will need to use the "Add and Remove Windows Components" function in the "Add and Remove Programs" control panel to add the SMTP server to your IIS installation. If you need to do this additional installation, once you've opened the "Add and Remove Windows Components", click on "Internet Information Services (IIS)" to highlight it and then click on the "Details" button (Figure 3). This will open an IIS dialog; examine this dialog to locate "SMTP Service" and click on it to place a check mark on the box (Figure 4). Once this item has been checked, click on the "OK" button to install the SMTP server.

Figure 3: Windows Components Wizard

Figure 4: Adding the SMTP Service to IIS

Once the default SMTP server has been installed or verified; you can now configure it to send email. In order to configure the SMTP server, open the IIS control panel, locate the default SMTP server icon in the treeview, and select and right click the icon. Once the context menu has been displayed, locate "Properties" and click on it to open the properties menu.

Once the Default SMTP Virtual Server Properties dialog is displayed click on the "Access" tab (Figure 5):

Figure 5: SMTP Property Dialog

Select the "Authentication" button to display the authentication options (Figure 6):

Figure 6: Authentication Options

Make sure that the Anonymous access option is checked and that all other options are unchecked; in some instances you may wish to use the other options but in most cases involving a public website this is the option you'd want. Once you have verified this setting, click "OK" to close this dialog.

Back to the "Access" tab, locate and click on the "Relay" button to display the relay options. Note that the radio button for "Only the list below" is selected, and that the local host IP address has been added to the list of computers permitted to relay through the SMTP server. Naturally this is OK for a development machine but in deployment, you would use the actual IP address of the web server. If no IP addresses are shown in the list, click on the "Add" button and add the IP address. Once finished, click on the "OK" button to accept the changes and to dismiss the dialog. (Figure 7)

Figure 7: Relay Restrictions Dialog

Next, select the "Delivery" tab from the SMTP Server properties dialog (Figure 8):

Figure 8: Delivery Options Dialog

From this dialog, select the "Advanced" button to reveal the advanced options dialog (Figure 10):

Figure 9: Advanced Delivery Dialog

From this dialog there are two points to make; first, the "Fully qualified domain name" property should be pre-populated; you may click on the "Check DNS" button to validate the setting. The next option is probably the most critical item for the whole shooting match; the Smart Host property has to be set to point to a valid SMTP mail server that will permit you to relay mail. For most cases, you will key in the name of your internet provider's default SMTP mail server; the address is likely to be in the format of "mail.something.com" where "something" is the internet provider's domain name. There are two easy ways to get this, one is, if you are using Outlook, open up Outlook and pull up the information on your email account; the mail server will be listed there. The second option is to guess and you can qualify your guess by pinging the server.

If your internet provider's name is "foxtrox", try pinging mail.foxtrot.com; if you get a response there, that is probably the one to use. If that does not work, contact your administrator and ask them for the information on the SMTP mail server; don't try to plug in an Exchange server, there will likely be an SMTP mail server out there even if your company is using an Exchange server. The only other hurdle is to make sure that the SMTP mail server will allow you to relay and again, you may need to talk to the administrator if the server bounces your mail. Once these settings are made, click on the "OK" button to save the settings and close the dialog.

The last thing to do is to check the security tab to make sure the accounts are properly configured; once done your security settings should look something like this (Figure 10):

Figure 10: Security Settings for the SMTP Server

Once everything is setup, click on the "OK" button to save the settings and to close the Default SMTP Virtual Server Properties dialog.

Getting Started:In order to get started, unzip the included project and open the solution in the Visual Studio 2008 environment. In the solution explorer, you should note these files (Figure 11):

Figure 11: Solution Explorer

The solution contains two projects; the EmailHandler project is a class library; it contains a class entitled, "Emailer.cs" which contains all of the code necessary to send email messages with and without attachments, and to validate email addresses. The second project is entitled, "EmailTestApp"; this is a win forms application which provides an interface that may be used to construct and send email messages using the Emailer class functions.

Code: Emailer.csThe Emailer class is used to send email messages with or without attachments, and it contains the code used to validate the format of the email addresses used to define the email sender and recipient.

The class begins with the default and added imports; note the addition of System.Net.Mail, System.Net.Mime, and System.Text.RegularExpressions.

usingSystem;usingSystem.Data;usingSystem.Configuration;usingSystem.Collections;usingSystem.Linq;usingSystem.Net.Mail;usingSystem.Net.Mime;usingSystem.Text.RegularExpressions;usingSystem.Web;The next section contains the namespace and class declarations.

namespaceEmailHandler{publicclassEmailer{The first method contained in this class is used to send an email message without an attachment. The method collects the recipient email address, the sender's email address, the email subject line, and the message body as arguments. Within the code, the recipient's email address is validated, a new mail message is constructed using the passed in arguments, an instance of an SMTP client is created using the mail server (stored in the properties), a Boolean is set to enable the requests to include the default credentials, and then the message is sent. If the action succeeds, the method will return a string indicating that the submittal was successful, else, the exception message will be returned to the caller.

//////Transmit an email message to a recipient without///any attachments//////Recipient Email Address///Sender Email Address///Subject Line Describing Message///The Email Message Body///Status Message as StringpublicstaticstringSendMessage(stringsendTo,stringsendFrom,stringsendSubject,stringsendMessage){try{// validate the email addressboolbTest = ValidateEmailAddress(sendTo);// if the email address is bad, return messageif(bTest ==false)return"Invalid recipient email address: "+ sendTo;// create the email messageMailMessagemessage =newMailMessage(sendFrom,sendTo,sendSubject,sendMessage);// create smtp client at mail server locationSmtpClientclient =newSmtpClient(Properties.Settings.Default.SMTPAddress);// add credentialsclient.UseDefaultCredentials =true;// send messageclient.Send(message);return"Message sent to "+ sendTo +" at "+DateTime.Now.ToString() +".";}catch(Exceptionex){returnex.Message.ToString();}}

That next method contained in the class is used to send an email message with an attachment. This method is the same as the previous with the exception being that it also accepts an array list containing the paths to files to be included as attachments to the message. The code is annotated and should be easy enough to follow.

//////Transmit an email message with///attachments//////Recipient Email Address///Sender Email Address///Subject Line Describing Message///The Email Message Body///A string array pointing to the locationof each attachment///Status Message as StringpublicstaticstringSendMessageWithAttachment(stringsendTo,stringsendFrom,stringsendSubject,stringsendMessage,ArrayListattachments){try{// validate email addressboolbTest = ValidateEmailAddress(sendTo);if(bTest ==false)return"Invalid recipient email address: "+ sendTo;// Create the basic messageMailMessagemessage =newMailMessage(sendFrom,sendTo,sendSubject,sendMessage);// The attachments array should point to a file location// where// the attachment resides - add the attachments to the// messageforeach(stringattachinattachments){Attachmentattached =newAttachment(attach,MediaTypeNames.Application.Octet);message.Attachments.Add(attached);}// create smtp client at mail server locationSmtpClientclient =newSmtpClient(Properties.Settings.Default.SMTPAddress);// Add credentialsclient.UseDefaultCredentials =true;// send messageclient.Send(message);return"Message sent to "+ sendTo +" at "+DateTime.Now.ToString() +".";}catch(Exceptionex){returnex.Message.ToString();}}

The last method contained in the Emailer class is used to validate an email address; this is accomplished by validating the address against a regular expression.

//////Confirm that an email address is valid///in format//////Full email address to validate///True if email address is validpublicstaticboolValidateEmailAddress(stringemailAddress){try{stringTextToValidate = emailAddress;Regexexpression =newRegex(@"\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}");// test email address with expressionif(expression.IsMatch(TextToValidate)){// is valid email addressreturntrue;}else{// is not valid email addressreturnfalse;}}catch(Exception){throw;}}}}

Code: frmTestEmailer.csThis form class is the only class contained in the test application; it provides a simple interface for building an email message with attachments.

The class begins with the default imports.

usingSystem;usingSystem.Collections;usingSystem.ComponentModel;usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Windows.Forms;usingEmailHandler;The next section contains the namespace and class declarations.

namespaceEmailTestApp{//////Test Application Form:///This application is used to test sending///email and email with attachments.///publicpartialclassfrmTestEmail:Form{Following the class declaration, an ArrayList is declared; this is used to contain the attachments added to the email message.

//////An arraylist containing///all of the attachments///ArrayListalAttachments;The next bit of code in the application is the default constructor.

//////Default constructor///publicfrmTestEmail(){InitializeComponent();}The next bit of code is the button click event handler used to add attachments to the email message. The code adds the file or files selected from an open file dialog to the array list used to store the file paths temporarily; the contents of the array list as written out and displayed in a text box contained on the form.

//////Add files to be attached to the email message/////////privatevoidbtnAdd_Click(objectsender,EventArgse){if(openFileDialog1.ShowDialog() ==DialogResult.OK){try{string[] arr = openFileDialog1.FileNames;alAttachments =newArrayList();txtAttachments.Text =string.Empty;alAttachments.AddRange(arr);foreach(stringsinalAttachments){txtAttachments.Text += s +"; ";}}catch(Exceptionex){MessageBox.Show(ex.Message,"Error");}}}

That next method contained in the class is used to exit the application. It is the button click event handler for the cancel button.

//////Exit the application/////////privatevoidbtnCancel_Click(objectsender,EventArgse){Application.Exit();}The last method contained in the test application form is used to send the email message. This is the button click event handler for the send button. This method verifies that all of the required elements of the message are available and then uses those form fields to supply each of the arguments used in the SendMessage or SendMessageWithAttachments methods contained in the Emailer class. Based upon whether or not the message contains any attachments, the appropriate method is called.

//////Send an email message with or without attachments/////////privatevoidbtnSend_Click(objectsender,EventArgse){if(String.IsNullOrEmpty(txtSendTo.Text)){MessageBox.Show("Missing recipient address.","Email Error");return;}if(String.IsNullOrEmpty(txtSendFrom.Text)){MessageBox.Show("Missing sender address.","Email Error");return;}if(String.IsNullOrEmpty(txtSubjectLine.Text)){MessageBox.Show("Missing subject line.","Email Error");return;}if(String.IsNullOrEmpty(txtMessage.Text)){MessageBox.Show("Missing message.","Email Error");return;}string[] arr = txtAttachments.Text.Split(';');alAttachments =newArrayList();for(inti = 0; i < arr.Length; i++){if(!String.IsNullOrEmpty(arr[i].ToString().Trim())){alAttachments.Add(arr[i].ToString().Trim());}}// if there are attachments, send message with// SendMessageWithAttachment call, else use the// standard SendMessage callif(alAttachments.Count > 0){stringresult =Emailer.SendMessageWithAttachment(txtSendTo.Text,txtSendFrom.Text, txtSubjectLine.Text, txtMessage.Text,alAttachments);MessageBox.Show(result,"Email Transmittal");}else{stringresult =Emailer.SendMessage(txtSendTo.Text,txtSendFrom.Text, txtSubjectLine.Text, txtMessage.Text);MessageBox.Show(result,"Email Transmittal");}}}}

IMAPIhttp://www.microsoft.com/downloads/en/details.aspx?FamilyID=63ab51ea-99c9-45c0-980a-c556746fcf05&DisplayLang=enDisabling Close Button on Forms

ByGiri Ganji| 13 Sep 2007

How to disable the Close button on C# WinForms

See Also

Articles like this Articles by this author

INCLUDEPICTURE "http://s.codeproject.com/images/share_drop16.png" \* MERGEFORMATINET

24

ArticleBrowse CodeStatsRevisions

4.42 (32 votes)

Sponsored Links

Top of Form

Introduction

To prevent the user from closing the form during data processing, it would be good if we disable the Close button on the form. Whenever it is required to show such a form with the Close button disabled, the first step is to look into the properties of the form to find the corresponding property. But I have found that form does not have such a kind of property provided by VS.NET/C#. Hence we need to do it programmatically and this article presents how to do it.

Background

In one of my projects, I had to implement a form with Close button disabled, so that the user cannot leave the form until it finishes the data processing. From the form designer window in VS.NET 2005, it is possible to hide the Minimize box and Maximize box. But there is no property called Close or Show close. Then I had some discussions with the team mates and got a couple of ways to do this. Among those alternatives, finally my idea got the nod. I thought of sharing this idea with The Code Project community and hence I have written this small article.

Using the Code

During construction and creation of theFormobject, .NET would use the default creation parameters available in the base classCreateParamsproperty. In fact,CreateParamsproperty is available inForms.Controlclass. In our form class (derived fromSystem.Windows.Forms.Form), override this property and modify the creation flags. For disabling the Close button use 0x200 to modify theClassStylemember of theCreateParams.

Collapse

//// source code // Code Snippet private const int CP_NOCLOSE_BUTTON = 0x200;

protected override CreateParams CreateParams

{

get {

CreateParams myCp = base.CreateParams;

myCp.ClassStyle = myCp.ClassStyle | CP_NOCLOSE_BUTTON ;

return myCp;

}

}

That's it! We are done with the coding.

Points of Interest

The trick here is to override theCreateParamsproperty in our Form with modified create flags. Directly copy the above piece of code and paste it to yourFormclass and it should work. Happy coding!!!

Bottom of Form

Using .NET to create a Windows XP 'Fading-pages' style Wizard

ByJohnWellsMCSD| 22 Dec 2002

Windows XP's 'Out-Of-Box-Experience' during its setup and activation, brought us a cool new style of Wizard with pages that fade, and a clean blue interface.

See Also

Articles like this Articles by this author

INCLUDEPICTURE "http://s.codeproject.com/images/share_drop16.png" \* MERGEFORMATINET

4

ArticleBrowse CodeStatsRevisions

3.93 (26 votes)

Sponsored Links

Top of Form

Introduction

Most of us have probably seen the neat-o wizard Windows XP uses, to configure the operating system for the first time. This portion of the setup is called the 'Out-Of-Box-Experience'. If you haven't seen it, it looks much like the above screen shot of anapplicationI am working on. Unfortunately, I haven't seen any programs taking on this new style, and being that it's so warm and fuzzy, I feel the community at large should be implementing this sort of wizard interface.

What is an OOBE style wizard?

Well, first off, I don't own a multi-billion dollar company with the resources to study user-acceptance of GUI design, nor do I have the artistic merit to create my own. For this reason, I tend to take my lead in this area from a certain company, which does have the resources to create usable interfaces. Anyways, Microsoft's Windows XP OOBE wizard, complete with snazzy blue colors and nifty fading pages is a visual treat. I don't know if it will make people more productive, but a wizard like this could be the cherry on top of your application.

Why it's not done

I believe the main reason this isn't that common (the author has only seen Microsoft do it) is that, it requires a black belt in API and some pretty good ninja moves to make a control (not a top-level form) alter it's opacity with time. There are a couple of options, you could use theWM_PRINTwindow message and get a picture of the background of the wizard form, you could create your own controls which inherit standard controls and add opacity functionality, or you could (egad...) play a video (don't try that one at home).

Pieces of the puzzle

Clean interface

Navigation buttons

Descriptive label for each page

Wizard-specific icon

Fading pagesMy method: A 'Smoke-and-Mirrors' solution

Smoke and Mirrors? We're programmers, we do things (uh) correctly! Well, hear my solution before you gafaw me.... The list above reads like just about any other wizard, and you can create this wizard just like you would any other wizard. This part is up to you to implement. A couple ofPictureBoxes, someLabels, some nice lines (I used two gradient lines I created in Photoshop and just popped them in my project, each is 3 pixels high). You may want to create aPanelobject for each page in your wizard, nothing special.

To cut to the chase, I tried a couple of methods and found the best place to accomplish the fade is during the 'next' button logic. You could create a routine namedShowPageor whatever. In my implementation, this routine knows which page is desired, and which page is currently shown. Here's an ordered list of the actions to be taken, followed by some sample code.

1. Create a newFormto be our 'Smoke'

2. Place thisFormabove our wizards'Panelarea

3. Set theForm's size to cover thePanelwe're fading

4. Ramp theForm'sopacityfrom 0%->100%

5. Hidethe currentPanel,Showthe new one

6. Ramp the firstForm'sopacityback down to 0%

7. Destroy the firstFormSample code

Collapse

'API for fading

Private Declare Auto Function AnimateWindow Lib _

"user32.dll" (ByVal hWnd As Integer, _

ByVal dwTime As Integer, _

ByVal dwFlags As Integer _

) As BooleanPrivate Sub ShowPage(ByVal intPage As Integer, ByVal intLastPage As Integer)

Dim frmSmoke Windows.Forms.Form = New Windows.Forms.Form()

With frmSmoke

.Location = New Point(Me.PointToScreen(New Point(0, 0)).X _

+ pnlTarget.Left, _

Me.PointToScreen(New Point(0, 0)).Y_

+ pnlTarget.Top)

.Size = pnlTarget.Size

.FormBorderStyle = Windows.Forms.FormBorderStyle.None

.BackColor = Color.FromArgb(71, 111, 214)

.Visible = False .ShowInTaskbar = False .StartPosition = Windows.Forms.FormStartPosition.Manual

End With Me.AddOwnedForm(frmSmoke)

'This API is *not* asynchronus - It returns when the blend is completed

AnimateWindow(frmSmoke.Handle.ToInt32, 100, AnimateStyles.Blend)

'm_Pages is an array of Panel objects

'Hide the old panel

m_Pages(intLastPage).Enabled = False m_Pages(intLastPage).Visible = False 'Set the current heading

lblHeading.Text = m_Pages(m_intCurrPage).Text

'Show the new panel

m_Pages(intPage).Enabled = True m_Pages(intPage).Visible = True m_Pages(intPage).BringToFront()

'Focus on the correct default control for this wizard page

FocusDefaultControl(intPage)

'We've done the switchero, fade out the smoke form to show the next page

AnimateWindow(frmSmoke.Handle.ToInt32, 100, AnimateStyles.Blend Or _

AnimateStyles.Hide)

'Clean up some resources

Me.RemoveOwnedForm(frmSmoke)

frmSmoke.Close()

frmSmoke = NothingEnd SubNotes

TheFormwe're using as 'smoke' is basically sitting on top of the wizardForm, it's setup such that we don't see that it exists on the screen, yet being a top-level window, we can fade it. For multi-platform compatibility, you may want to create your own fading functions usingForm.Opacityand aTimer. I've used an API function that only works with Windows 2000 and above, but the actual implementation is up to you, this is just one of many methods that could create an OOBE style wizard.

Additional Notes: It can be observed that at least one page of the Windows XP OOBE Wizard actually cross-fades pages, that is, two pages are semi-transparent and the same time, however, with this method, pages must fade into a solid color or background image before fading into the next page.

Enjoy, and vote if you've enjoyed this (It gives me a warm-and-fuzzy feeling that you can't buy with money).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be foundhereBottom of Form

GripPanel

BySimmoTech| 19 Oct 2003

A WinForms Panel that shows a size grip when docked at the bottom of a form.

See Also

Articles like this Articles by this author

INCLUDEPICTURE "http://s.codeproject.com/images/share_drop16.png" \* MERGEFORMATINET

11

ArticleBrowse CodeStatsRevisions

3.57 (6 votes)

Sponsored Links

Top of Form

Introduction

This is not an earth-shattering new development but you might find a use for it in your toolbox.

It is simply a control derived from the standard WinformsPanelcontrol that draws a sizing grip when thepanelis docked at the bottom of a form.

Background

This control was written to get around a bug in Winforms that prevents controls anchored to the right and bottom of a form working correctly in a derived form.

I simply wanted a base form withOKandCancelbuttons anchored to the bottom right of the form but unfortunately, when a derived form was resized in the designer, the next compile moved the buttons to incorrect positions.

This is documented in Microsoft Knowledge Base Article 316560[1]but the workarounds are:-

1. Do not resize the form in the designer. (Which is not convenient!)

2. Change the modifier fromprivatetoprotected. (Which my controls already were, so this is incorrect)

My solution to this problem was to add apanel, all docked to the bottom of the form, with the required buttons (and some empty labels to provide spacing) docked to the right within that panel. This worked fine but it means that the form sizing grip was no longer visible.

So I decided to write a derivedPanelthat would have a sizing grip! What should have been a simple 5-minute job left me burning the midnight oil determined to find out how to do this.

How it works

I used Lutz Roeder's excellent Reflector for .NET[2]to look intoSystem.Windows.Forms.Dllto see how Microsoft did it for a form and I also found an article by Karl E. Peterson called "Get a Grip With SubClassing"[3]written for VB which does a similar thing.

The first part, drawing the size grip itself is easy.ControlPaint.DrawSizeGrip(part of WinForms) will draw a size grip of any size onto aGraphicsobject such as that provided in thePaintEventArgsparameter theOnPaintmethod.

The next bit was to make the cursor change shape and actually resize the form when the mouse pointer is over the size grip. To do this, I had to override theWndProcmethod and look forWM_NCHITTESTmessage. When this message is received, I check whether the mouse pointer is within the size grip and if so, basically lie to Windows and tell it that the mouse pointer is in the lower-right corner of a border of a resizable window. At this point, Windows takes over and does all that is necessary!

Finally, when the window is being resized, I need to invalidate the rectangle containing the size grip so that it will be redrawn and not leave 'bits' behind. At first, I Invalidated just the rectangle containing the grip but when the form was resized quickly, some bits were still left behind. Using Reflector again, I found that Microsoft took the easy option and Invalidated the whole control so I did the same!

Oh, and I also added a dividing line at the top of the panel usingControlPaintinOnPaintagain. This is optional.

References

1. Microsoft website2. http://www.aisto.com/roeder/dotnet/3. http://archive.devx.com/premier/mgznarch/vbpj/1999/06jun99/ap0699.pdfSource code

Here is the source code in full:

Collapse

using System;

using System.Drawing;

using System.Runtime.InteropServices;

using System.Windows.Forms;

namespace SimmoTech.Forms {

/// /// This control works exactly the same as a normal

/// panel but, if the panel is docked at the bottom of the form,

/// then a sizing grip is added and a horizontal 3D line

/// is added at the top of panel.

/// public class GripPanel: Panel {

private const int HTBOTTOMRIGHT = 17;

private const int WM_NCHITTEST = 0x84;

private const int WM_SIZE = 0x05;

/// /// Catch some windows messages

/// /// protected override void WndProc(ref Message m) {

// Only catch messages if the panel is docked to the bottom

if (Dock == DockStyle.Bottom) {

switch (m.Msg) {

// If the panel is being resized then we

// need to redraw its contents

case WM_SIZE:

Invalidate();

break;

// If the system is asking where the mouse pointer is,

// we need to check whether it is over our sizing grip

case WM_NCHITTEST:

// Convert to client co-ordinates of parent

Point p = FindForm().PointToClient(new Point((int) m.LParam));

int x = p.X;

int y = p.Y;

Rectangle rect = Bounds;

// Is the mouse pointer over our sizing group?

// (Use 12 pixels rather than 16 otherwise

// too large an area is checked)

if (x >= rect.X + rect.Width - 12 &&

x = rect.Y + rect.Height - 12 &&

y