Sunday 6 November 2011

SPGridView Part 2 - Custom Filters

The SharePoint 2007 SPGridView control allows easy filtering by setting the AllowFiltering property to true, but it generates the list of possible filter options by selecting all of the distinct values for the column when the column header is clicked. In a DB table I'm using with an SPGridView I store integer values of 0 to represent unset, 1 for low, 2 for medium and 3 for high and so the available filter options are 0, 1, 2 or 3.

To allow filtering by the associated text value, I check for a callback in my WebPart's override of CreateChildControls and if the column header in question has been clicked, bind the SPGridView control to a temporary data table which has a column with the same name as the one clicked containing all of the required values:
if (Page.IsCallback && !string.IsNullOrEmpty(Page.Request.Form["__CALLBACKPARAM"]))
                {
                    string[] param = Page.Request.Form["__CALLBACKPARAM"].Split(';');
                    if (param[1] == myColumn)
                    {
                        DataTable dt = new DataTable();
                        dt.Columns.Add(param[1]);
                        dt.Rows.Add("Unset");
                        dt.Rows.Add("Low");
                        dt.Rows.Add("Medium");
                        dt.Rows.Add("High");

                        m_SpGridView.DataSourceID = null;
                        m_SpGridView.DataSource = dt;
                        m_SpGridView.DataBind();
                    }
                } 

Also in CreateChildControls, if it isn't a callback to get the available filter options, I check for an actual filter call or a clear filter call and flip the text value back to the associated id if required (storing the filter in the ViewState) using a call to the following:
protected virtual void CheckFilter()
        {
            // If there is a call back with an event argument and event target and the target is our gridview...
            if (Context.Request.Form["__EVENTARGUMENT"] != null && Context.Request.Form["__EVENTTARGET"] != null &&
                Context.Request.Form["__EVENTTARGET"].EndsWith(m_SpGridView.ID))
            {
                string search = "__SPGridView__;__Filter__;";
                if (Context.Request.Form["__EVENTARGUMENT"].Equals("__SPGridView__;__Filter__;__ClearFilter__"))
                {
                    ViewState.Remove("FilterExpression");
                }
                else if (Context.Request.Form["__EVENTARGUMENT"].StartsWith(search))
                {
                    string[] newFilter = Context.Request.Form["__EVENTARGUMENT"].Replace(search, "").Split(';');

                    // If this is a custom filter field, switch the selected value for the actual value
                    if (newFilter[0] == myColumn)
                    {
                        if (newFilter[1] == "Unset")
                            newFilter[1] = "0";
                        else if (newFilter[1] == "Low")
                            newFilter[1] = "1";
                        else if (newFilter[1] == "Medium")
                            newFilter[1] = "2";
                        else if (newFilter[1] == "High")
                            newFilter[1] = "3";
                    }

                    // Set the filter in the viewstate
                    ViewState["FilterExpression"] = string.Format("{0}='{1}'", newFilter[0], newFilter[1]);
                }
            }
        }

Finally, override OnPreRender and set the FilterExpression of the SqlDataSource control associated with the SPGridView to the query stored in the ViewState:
protected override void OnPreRender(EventArgs e)
        {
            m_SqlDataSource.FilterExpression = string.Empty;
            if (ViewState["FilterExpression"] != null)
                m_SqlDataSource.FilterExpression = ViewState["FilterExpression"].ToString();

            base.OnPreRender(e);
        }

Saturday 5 November 2011

SPGridView Part 1 - Multiple SPGridViews

There appears to be a bug clearing filters when using multiple instances of the SharePoint 2007 GridView (SPGridView) control on the same page. In my case I had two very similar custom WebParts on the same page using SPGridView controls to display data pulled from a database.

If filtering is enabled on the controls, when the user clears the filter on the second SPGridView on the screen, the filter on the first SPGridView is cleared instead.

After some searching, I found a nice solution to this problem here.

I copied the suggested solution into a class that inherits from SPGridView and used that in my WebParts to resolve the problem:
public class MySPGridView : SPGridView
    {
        // Override the OnPreRender to databind and then fix each headerrow control
        protected override void OnPreRender(EventArgs e)
        {
            DataBind();
            if (this.HeaderRow != null)
            {
                foreach (WebControl control in this.HeaderRow.Controls)
                {
                    UpdateTemplateClientID(control);
                }
            }
            base.OnPreRender(e);
        }

        // Fix the ClientOnClickPreMenuOpen property of a menu control
        private void UpdateTemplateClientID(Control control)
        {
            if (control is Microsoft.SharePoint.WebControls.Menu)
            {
                Microsoft.SharePoint.WebControls.Menu menuControl = control as Microsoft.SharePoint.WebControls.Menu;
                string jsFunctionCall = menuControl.ClientOnClickPreMenuOpen;
                menuControl.ClientOnClickPreMenuOpen = jsFunctionCall.Replace("%TEMPLATECLIENTID%", this.ClientID + "_SPGridViewFilterMenuTemplate");
            }
            else if (control.HasControls())
            {
                foreach (WebControl c in control.Controls)
                    UpdateTemplateClientID(c);
            }
        }
    }

Friday 26 August 2011

XBMC on the Apple TV 2

As previously mentioned, I've been playing with XBMC on the Apple TV 2.

Its worth noting that you need to finish running through the Apple TV setup (selecting language and location etc.) before you can access the device. This needs to be done after updating the firmware.

After installing the latest official build using these instructions I wanted to try the nightly builds. When attempting to update XBMC to a nightly version using the instructions on the same page, dpkg would halt on:
Preparing to replace org.xbmc.xbmc-atv2 10.0-9 ...

It turns out that because my Apple TV is running iOS 4.3, rm -rf which is used to remove the previous version when upgrading, is broken. I therefore needed to manually remove the previous version before updating using the following:
rm -r /Applications/XBMC.frappliance

Skins can be added manually to the Apple TV 2 build of XBMC by SSH'ing to it and downloading them with wget to the addons directory and unzipping:
cd /private/var/mobile/Library/Preferences/XBMC/addons/
wget <url of skin.zip>
unzip <skin zip file >
rm <skin zip file >
The skin can then be selected in XBMC (I'm using 'Night' at the moment).

If using an advancedsettings.xml for custom settings, this needs to go here on the Apple TV 2:
/private/var/mobile/Library/Preferences/XBMC/userdata
And the ever useful log file is at:
/private/var/mobile/Library/Preferences/xbmc.log

A Shared MySQL Database for XBMC

I want to use XBMC to stream media from my home server to various devices on my network. XBMC supports using a central MySQL database to store details about media such as the cast of a movie which can be accessed by any device on the network. This means that all devices are in sync, media only has to be scanned once rather than on each device and if you stop playback on one device you can resume from the same place on another device.

I followed this guide to set up the database.

I had to open port 3306 for TCP in the Vista firewall to allow devices to access to the database.

XBMC threw up some errors and I found that I had to scrap the databases I had created (xbmc_video and xbmc_music) and build them again with the following commands:
CREATE DATABASE xbmc_video CHARACTER SET latin1 COLLATE latin1_general_ci;
CREATE DATABASE xbmc_music CHARACTER SET latin1 COLLATE latin1_general_ci;

After getting it working with a XBMC 10.01 for Windows, I installed a nightly build of XBMC on an Apple TV 2 and found I had problems with the Apple TV build expecting a different database version. It looks like you need to be careful about using builds that that expect the same database version. XBMC creates the required tables automatically when scanning the library provided it can get to the DB and has access, so to rebuild the DB to a different version you just need to drop the tables (or drop and recreate the databases) and re-scan the library from XBMC.

The advancedsettings.xml file used by XBMC to enable the MySQL DB needed to go in the following locations:
Windows - C:\Users\<USERNAME>\AppData\Roaming\XBMC\userdata
Apple TV 2 - /private/var/mobile/Library/Preferences/XBMC/userdata

The XBMC log files are really helpful in diagnosing issues like these and are in the following locations:
Windows - C:\Users\<USERNAME>\AppData\Roaming\XBMC\xbmc.log
Apple TV 2 - /private/var/mobile/Library/Preferences/xbmc.log

Vista on the HP Proliant Microserver

I recently got an HP Proliant Microserver (which currently has a £100 cash back offer from HP) to store media and backups on. This is a little server box with 4 drive bays sporting a low power dual core AMD processor. I've added 4GB of DDR3 ECC RAM to give 5GB and as well as the 250GB HDD that comes with it, added a 2TB drive for storage. I had a spare copy of Vista so its currently running that while I wait for Windows Home Server 2011 to arrive.

I had a couple of issues installing Vista on it. First of all I had to change the SATA controller to IDE mode in the bios to get the installer to detect the hard drive.

Vista didn't find an ethernet driver. The machine has a Broadcom NetXtreme BCM5723 ethernet controller so I was able to use a driver from the Broadcom site.

Finally, rather than use the standard VGA driver, I used ATI Catalyst Mobility utility to pull down the appropriate graphics driver.

Vista runs a little sluggish on this machine but it should be fine for a little share sharing until WHS 2011 arrives.

Sunday 22 May 2011

SSDP-Tester

I've been looking into UPNP and DLNA which uses SSDP for device discovery, so I put together a win forms tool in C# to monitor SSDP traffic and send discovery requests.



The code can be found here.

Saturday 14 May 2011

RC4

I've been reading up on the PDF file format which lead me to reading about RC4. It looked pretty simple (and is), so I decided to knock up a quick implementation in C# based on the information on Wikipedia. The results can be found here.

To use it, include the namespace

using randomworks;

Then do something like this to turn a plaintext string into a ciphertext int array:

string MyKeyString = "Key";
string MyPlaintext = "Plaintext";

RC4 myRC4 = new RC4();
myRC4.SetKey(MyKeyString);
int[] ciphertext = myRC4.Encrypt(MyPlaintext);

If you want to turn the int array into a nice hex string, do something like this:

StringBuilder sb = new StringBuilder();
for (int i=0; i<ciphertext.length; i++)
{
string s = ciphertext[i].ToString("x2");
sb.Append(s);
}
txtCiphertext = sb.ToString();


To decrypt, just pass the decrypt method an encrypted int array and it'll return a decrypted string.

If you find this useful or notice any mistakes, please let me know.