Saturday, May 21, 2011

SQL XML with Access

The previous post discussed a SurfaceView object model concept for managed code add-ins. This post shares how to use SQL Server 2005, and above, XML features to implement stored procedure array parameters. The SQL XML code uses the sample Northwind SQL Server database as well as the SurfaceView model. The example scenario sends a <Products><ProductID> xml list that returns a non-contiguous list of products.
Note      The sample code, located at http://desktopweb.codeplex.com, contains a sql script that adds the SQL CML code to the Northwind sample database. The Northwind sample database is also available at http://desktopweb.codeplex.com.
SelectByIdList Stored Procedure

CREATE PROCEDURE SelectByIdList
(
      @productIds xml
)
AS

SET ARITHABORT ON

-- @Products table for Products Non-contiguous XML query
DECLARE @Products TABLE (ProductID int)

INSERT INTO @Products (ProductID) SELECT ParamValues.ProductID.value('.','INT')
FROM @productIds.nodes('/Products/ProductID') as ParamValues(ProductID)

SELECT * FROM
    Products
INNER JOIN
    @Products p
ON    Products.ProductID = p.ProductID


-- Test the procedure
EXEC SelectByIdList @productIds=
'<Products><ProductID>37</ProductID><ProductID>6</ProductID>
<ProductID>15</ProductID><ProductID>3</ProductID></Products>'

SelectByIdList() Method
Once the SelectByIdList stored procedure is added to the Northwind database, you need to write some ADO.NET code to call SelectByIdList @productIds sending the stored procedure a XElement containing a non-contiguous list of products. Using SQL XML parameters provides a flexible means to query SQL Server.

ADO.NET Code

private DataTable sp_SelectByIdList(XElement idElements)
{
  using (StringWriter swStringWriter = new StringWriter())
  {
   using (SqlConnection dbConnection =
  new SqlConnection
  (MyDataAddin.Properties.Settings.Default.NorthwindConnectionString))
     {
       using (SqlCommand dbCommand = new SqlCommand("SelectByIdList", dbConnection))
       {
         dbCommand.CommandType = CommandType.StoredProcedure;
         SqlParameter parameter = new SqlParameter();
         parameter.ParameterName = "@productIds";
        parameter.DbType = DbType.Xml;
         parameter.Direction = ParameterDirection.Input; // Input Parameter 
         parameter.Value = idElements.ToString();
         dbCommand.Parameters.Add(parameter);
         dbConnection.Open();

         SqlDataAdapter da = new SqlDataAdapter(dbCommand);
         northwindDataSet.sp_SelectByIdList.Clear();
         da.Fill(this.northwindDataSet.sp_SelectByIdList);
         return this.northwindDataSet.sp_SelectByIdList;
       }
     }
   }
  }

Create Products XElement

private void selectButton_Click(object sender, EventArgs e)
{
  XElement xmlIdElements = new XElement("Products");

  foreach (DataGridViewRow row in vw_ProductListDataGridView.Rows)
  {
  if (true == Convert.ToBoolean(row.Cells["SelectCheckbox"].Value))
   {
xmlIdElements.Add(new XElement("ProductID", Convert.ToInt32(row.Cells["ProductIDColumn"].Value)));
   }
  }
  //Bind the DataGridView
  sp_SelectByIdListDataGridView.DataSource = sp_SelectByIdList(xmlIdElements);
}


To run the sample, download and setup the source from http://desktopweb.codeplex.com/SourceControl/changeset/changes/7973.  You might need to change the NorthwindConnectionString within Settings.Settings to point to your SQL Server instance.
To run the sample
1)      Open the Dashboard form by clicking the Dashboard ribbon button
2)      Click the SQL Xml Table ribbon button to view the data grid task pane

When you click a product row, the data in the form is bound to the DataGridRow. As you can see from the example, the form is now bound to Access data and SQL Server data. The sample code partially implements ADO.NET data binding. Hopefully, this might get you started bringing SQL Server data and ADO.NET into your Access solutions.
BindForm
public void BindForm(DataRow currentRow)
{
  access.TextBox textBox;

   //Fill Form from BindingSource
   //Set Form Textbox values based on ColumnName match
   foreach (DataColumn c in currentRow.Table.Columns)
   {
     if (ThisDatabase.AllForms["Dashboard"].FindControl(c.ColumnName))
       {
textBox = ThisDatabase.AllForms["Dashboard"].Controls(c.ColumnName) as access.TextBox;
         textBox.Value = currentRow[c.ColumnName].ToString();
       }
   }

   textBox = null;
   }
  }
}


Navigate a DataTable
int rowPosition = 0;
void prevCommand_Click()
{
  if (this.SqlXmlDataGrid != null && this.SqlXmlDataGrid.Visible)
  {
   if (rowPosition > 0)
   {
     rowPosition--;

this.SqlXmlDataGrid.BindForm
(this.SqlXmlDataGrid.CurrentDataRow.Table.Rows[rowPosition]);
   }
  }
}

void nextCommand_Click()
{
  if (this.SqlXmlDataGrid != null && this.SqlXmlDataGrid.Visible)
  {
   if (rowPosition + 1 < this.SqlXmlDataGrid.CurrentDataRow.Table.Rows.Count)
   {
     rowPosition++;
    
     this.SqlXmlDataGrid.BindForm
     (this.SqlXmlDataGrid.CurrentDataRow.Table.Rows[rowPosition]);
   }
  }
}


Monday, May 2, 2011

Managing Access Add-ins with a SurfaceView

Extending Microsoft Access solutions with managed code has traditionally been a challenge. Visual Studio Shared Add-ins have been available for some time; however, managing the details of releasing Access.exe after closing an Access application has been a challenge. This article presents a SurfaceView object model that manages the release of Access form and report controls. After discussing the SurfaceView, the article suggests effective ways to extend Access applications using SurfaceView, Ribbon controls, custom task panes, a custom Access form and Windows Workflow 4.0. The code snippets used within the article are contained within the source code located at http://desktopweb.codeplex.com/SourceControl/changeset/changes/7526.

Topics

Using the SurfaceView object model to manage an Access .NET add-in enables extensibility opportunities beyond traditional VBA. This article presents several how to topics to get you started extending Access with managed code.
  • Setup the sample including troubleshooting Add-in setup
  • Use the sample
  • Use a SurfaceView to manage forms and reports
  • Use a ribbon button
  • Create custom task panes
  • Create a custom Access popup form
  • Use Windows Workflow 4.0 to automate Access
Please note that the SurfaceView model is a sample of a potential object model to manage Access objects. As with any sample, the concepts and source code have not been thoroughly designed or tested and are not intended for production use.
The remainder of this blog is in pdf format …

PDF Article

Sunday, April 10, 2011

Source code update

This week we will post new code to http://desktopweb.codeplex.com. The following screenshot shows the Access HTML editor improvements.

image

HTML Editor enhancements:

  • Open content stored in an Access table
  • Save content
  • Common formatting including bold, italic, underline, fonts and colors
  • Undo, cut, copy and paste
  • Numbered list, bulleted list, outdent and indent
  • DataLinks
  • Find text
  • Insert content via a HyperBar control (not fully implemented)

Access Addin stability is another significant improvement. An abstract SurfaceController class and corresponding FormController and ReportController class was added to provide a clean and stable model to manage Access form and report controls. Access controls can be referenced as ThisDatabase.AllForms["Dashboard"].Controls(“Budget”). The SurfaceController handles the details including ensuring that Access is always released when closed.

Silverlight WebWidgets can be included in a database as an attachment and deployed to the client.

The source code also shows more Windows Workflow 4.0 to add .NET features into Access. The idea is to create common CodeActivities that others can drop into a Workflow to automate Access.

I will upload the new source code and provide more documentation later this week …

Friday, February 18, 2011

Access HTML Editor with Silverlight and Workflow

One of the goals of my blog is to experiment with integrating .NET into Office. Lately, the emphasis has been to show Silverlight in Access. Another area of interest is evaluating how Windows Workflow 4.0 could be used in Access to create workflow solutions as well as enable a declarative development environment. This blog, and accompanying CodePlex code download, refines the Silverlight and web browser control concept and introduces a simple Windows Workflow 4.0 containing two custom activities.
The Silverlight and web browser control concept is refined by showing how to create a simple Access HTML editor containing an embedded Silverlight Treeview control. An HTML Editor can be created primarily by using a contentEditable attribute and calling an HtmlDocument execCommand()  method or using mshtml.IHTMLTxtRange methods to style a span element. An embedded Silverlight Treeview control can be displayed by creating an HtmlDocumentEvent.onclick delegate and setting the Dashboard.TreeviewPage.Display style to none or block. Here is an overview of how this works. You can go to http://desktopweb.codeplex.com/SourceControl/list/changesets for a complete sample.

HTML Editor with Embedded Silverlight


HTML formatting
Commands
Dashboard.HtmlDocument.execCommand("Bold");
Dashboard.HtmlDocument.execCommand("Italic");

Style a span element
mshtml.IHTMLTxtRange rng =
  (mshtml.IHTMLTxtRange)Dashboard.HtmlDocument.selection.createRange();
     
…
rng.pasteHTML("<span style='color:" + Dashboard.ColorCombo.Text + ";'>" + text + "</span>");

//Reselect text
rng.moveStart("character", -text.Length);
rng.select();

…

TreeviewPage

Create event delegates after HTML document is complete

void MyDocumentTasks_onDocumentComplete(object sender, EventArgs e)
{
Dashboard.HtmlDocumentEvent.onclick += new mshtml.HTMLDocumentEvents2_onclickEventHandler(HtmlDocumentEvent_onclick);
     
…      

//Initially hide the Treeview
      Dashboard.TreeviewPage.Display = "none";
}


Toggle TreeviewPage display

bool HtmlDocumentEvent_onclick(mshtml.IHTMLEventObj pEvtObj)
{
       if ("popup" == pEvtObj.srcElement.id.ToString())
       {
if(Dashboard.TreeviewPage.Display == "none")
Dashboard.TreeviewPage.Display = "block";
else
Dashboard.TreeviewPage.Display = "none";
}
return true;
}

Invoke Windows Workflow 4.0 in Access
Adding Windows Workflow 4.0 (WF4) enables workflow and declarative development into Access. To experiment with WF4 in Access, a simple project budget workflow was created containing an If activity and two custom activates; ValidValueActivity and EmailActivity. An If activity executes ValidValueActivity if the budget threshold is less than 1000; otherwise, EmailActivity is executed. To show how a custom activity could be designed for Access, each activity sets the Cost Textbox BorderColor. In addition, the ValidValueActivity adds an item to the Silverlight TreeviewPage control.
Note
An assembly reference must be added to a workflow xaml file; otherwise, a cannot create unknown type XamlObjectWriterException is thrown. You will need to append assembly=MyDataAddin to the xmlns:local Activity attribute.

                Default  Activity  Element
<Activity xmlns:local="clr-namespace:MyDataAddin" …

Change to
<Activity xmlns:local="clr-namespace:MyDataAddin;assembly=MyDataAddin" …

Example Workflow

Dashboard_onAfterUpdate

The onAfterUpdate event delegate Invokes the example XAML workflow.
void Dashboard_onAfterUpdate(object sender, EventArgs e)
{
int value;
      Int32.TryParse(Dashboard.TreeviewCheckbox.Value.ToString(), out value);
     
      String fullFilePath =
            @"C:\Projects\desktopWeb\MyDataAddin\WorkflowLibrary\ExampleWorkflow.xaml";

      Activity wf = (Activity)ActivityXamlServices.Load(fullFilePath);

      IDictionary<String, Object> output = WorkflowInvoker.Invoke(
            wf, new Dictionary<String, Object>
                    {
                        {"ArgBudget", Convert.ToInt32(Dashboard.Cost.Value)},
                    });

MessageBox.Show(output["ResultString"].ToString(), "Windows Workflow");
}

Code Activities

public sealed class EmailActivity : CodeActivity, ISimpleActivity
{
public OutArgument<Int32> ResultCode { get; set; }
      public OutArgument<String> ResultString { get; set; }

      protected override void Execute(CodeActivityContext context)
      {
              //This example returns -1 for failure
              //A production application would send email to the manager
              ResultCode.Set(context, -1);
              ResultString.Set(context, "Email sent to your manager");

              Dashboard.Cost.BorderColor = VB.Information.RGB(255, 0, 0);
      }
}

public sealed class ValidValueActivity : CodeActivity, ISimpleActivity
{
public OutArgument<Int32> ResultCode { get; set; }
       public OutArgument<String> ResultString { get; set; }

       protected override void Execute(CodeActivityContext context)
       {
              //This example returns 1 for success
              ResultCode.Set(context, 1);
              ResultString.Set(context, "Valid value");

              Dashboard.Cost.BorderColor = VB.Information.RGB(0, 0, 255);

              Dashboard.TreeviewPage.AddRootItem(Dashboard.Title.Value.ToString());
}
}

Example Workflow

 

Wednesday, February 2, 2011

Access HTML Editor Example

Tonight I added an improved example of how to create a Heading 1 style and add and remove a color style. This is useful for an Access HTML Editor using the Web Browser Control. Note how one can program DHTML from within an Access .NET Addin.

Apply an H1 element

void Heading1_Click()
{
Tasks.HtmlDocument.execCommand("formatblock", false, "<H1>");
}

Apply a color style

void ColorCombo_Change()
{   
//Apply style
try
{
mshtml.IHTMLTxtRange rng = (mshtml.IHTMLTxtRange)Tasks.HtmlDocument.selection.createRange();
             
string text = rng.text;
       var elem = rng.parentElement();

       if (elem.tagName != "SPAN")
       {
rng.pasteHTML("<span style='color:" + Tasks.ColorCombo.Text + ";'>" + text + "</span>");

              rng.moveStart("character", -text.Length);
              rng.select();
      }else if (elem.tagName == "SPAN" && elem.style.color != string.Empty)
      {
      if (Tasks.ColorCombo.Text != string.Empty)
elem.style.color = Tasks.ColorCombo.Text;
      else{
              //Remove SPAN element
              rng.pasteHTML(text);

              rng.moveStart("character", -text.Length);
              rng.select();
      }
   }

Tasks.WebBrowser.SetFocus();
   }
catch (Exception) { }
}