A nice hack to execute a stored proc and filter the results in SQL, plus how to generate a create table statement for a table the same structure as a view

OK, so it’s unlikely anyone but me will ever want to do this, but that’s OK… Sometimes I leave stuff here for myself to reuse later.

I’m debugging some code and had this weird case where I want to simulate something, in SQL Server Management Studio, that I’m already doing in code. In the code, it’s easy… Just take the results and filter using Linq. But what I really want to do, to see the same data in a management studio grid, is to be able to quickly write something similar to a select statement with a filter but from a stored procedure, where I add a where clause after calling exec.

Well, this doesn’t really do that. Instead, it executes the stored proc, puts the results into a temp table, and then selects with a where clause from the temp table before dropping said table. But I still think it’s pretty cool. The script looks like this:

-- Create a temporary table
CREATE TABLE #TempTable (
    -- Define columns as per the result set of the stored procedure
    Column1 DATATYPE,
    Column2 DATATYPE,
    ...
)

-- Execute the stored procedure and insert results into the temporary table
INSERT INTO #TempTable (Column1, Column2, ...)
EXEC YourStoredProcName

-- Select from the temporary table with the desired filter
SELECT *
FROM #TempTable
WHERE OperationType = 'aq'

-- Drop the temporary table once done (optional)
DROP TABLE #TempTable

But wait, that’s not all… My next problem is figuring out the create table statement for the temporary table. Some of the SQL gets quite complicated around here and writing a statement to do that could take much longer than I’d like it to, but… it just so happens that my stored proc is one of a few I wrote that simply filters based on different conditions from the same view.

And now maybe this is useful to others after all. I know I, for one, would often like to create a temp table with the same fields and types as that of a view. And it turns out that generating the create table statement for such a view is quite easy, especially if you ask ChatGPT to do the work for you. This worked for me. Of course I can’t show you the final query because those fields belong to my employer, but this query to generate the create table statement was generated by ChatGPT anyway. Enjoy! (Just change ’YourViewNameHere’ to the name of your view.)

My only other change to my final SQL, besides using this to replace the create statement at the top, was to change the insert statement not to specify column names, because I insert into all the columns.

DECLARE @TableName NVARCHAR(MAX) = 'YourViewNameHere'
DECLARE @CreateTableStatement NVARCHAR(MAX) = ''

SELECT @CreateTableStatement = @CreateTableStatement +
    '[' + COLUMN_NAME + '] ' + DATA_TYPE + 
    CASE 
        WHEN CHARACTER_MAXIMUM_LENGTH IS NOT NULL THEN '(' + CAST(CHARACTER_MAXIMUM_LENGTH AS NVARCHAR(MAX)) + ')'
        ELSE ''
    END + ',' + CHAR(13)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName

SET @CreateTableStatement = 'CREATE TABLE tempTable (' + CHAR(13) + @CreateTableStatement + ')'

-- Print the CREATE TABLE statement
PRINT @CreateTableStatement
Posted in SQL | Tagged , | Leave a comment

Can you see the bug?

OK, I would not normally share my bad code, but I’m laughing tool hard… Sorry.

History – There’s an existing issue with some legacy code being called that needs to be optimized. The legacy code sometimes gets stuck and uses 100% CPU, causing the application to use way too much CPU for a long time. I am the only developer working on this system now, and although I have raised the issue, there are three new projects I must get to, so I have to work around rather than fixing the code.

But, can you see the bug in my workaround? It’s a rookie mistake, which is unfortunate because I’m no rookie. I’ll give another clue after the code…

private async Task HandleCpuUsage()
{
    float currentCpuUsage = await GetCpuUsageAsync(); // Get current CPU usage asynchronously

    InfoMessage(string.Format("CPU Usage: {0:0.00}%", currentCpuUsage));

    cpuUsageStack.Push(currentCpuUsage); // Push current CPU usage onto the stack

    if (cpuUsageStack.Count >= 5 && cpuUsageStack.All(usage => usage > 90))
    {
        InfoMessage("Restarting application because of high CPU usage.");
        RestartHandler.RestartApplication();
    }

    // Limit the stack size to 5
    while (cpuUsageStack.Count > 5)
    {
        cpuUsageStack.Pop();
    }
}

You see it, or need a clue?

.

.

.

.

.

.

Clue: Stack vs Queue; I often mix them up. Don't tell anyone smile

Posted in Programming | Tagged , | Leave a comment

Refresh all your stored procedures and views, and wipe the query cache in SQL Server.

In my spare time while waiting for user stories and shit, I’ve been spending quite a bit of time in the Query Store in SQL Server 2019. It’s pretty cool – you should check it out if you haven’t already. In a nutshell, it gives you one place where you can see all your poorly performing queries, whether they’re missing indexes or just plain bad queries that need to be rewritten. I could probably write a bunch about using it, but I’m not going to because of lack of time, as usual. I’m doing this besides my actual work anyway, though I have found it quite fun and I have made a measurable difference in some places.

Anyway, the only issue I’ve run into is that sometimes, for whatever reason, SQL Server doesn’t use the better query plans it could use after I’ve added indexes. So, me and my buddy ChatGPT came up with this slight overkill of a script to try forcing SQL to use the best query plans. Basically just tell SQL to ignore its cache and recompile everything. Probably total overkill because telling it to recompile the tables will already have it recompile the procs that use them. But regardless, this is safe to run in production. At your own risk of course…

My file is saved as RefreshAllprocsAndViews.sql, but you can call it whatever the fuck you want. Enjoy.

EXEC sp_msforeachtable 'EXEC sp_recompile ''?'''

-- Recompile all stored procedures
DECLARE @procedureName NVARCHAR(128)

DECLARE procedureCursor CURSOR FOR
SELECT name
FROM sys.procedures
WHERE name NOT LIKE 'sys.sp_%'
      AND name NOT LIKE 'xp_%'

OPEN procedureCursor
FETCH NEXT FROM procedureCursor
INTO @procedureName

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRY
        EXEC sp_recompile @procedureName
    --PRINT 'Object ' + QUOTENAME(@procedureName) + ' was successfully marked for recompilation.'
    --ABove output not necessary for stored procedures.
    END TRY
    BEGIN CATCH
    -- Handle any errors if needed
    END CATCH
    FETCH NEXT FROM procedureCursor
    INTO @procedureName
END

CLOSE procedureCursor
DEALLOCATE procedureCursor

-- Recompile all views
DECLARE @viewName NVARCHAR(128)

DECLARE viewCursor CURSOR FOR
SELECT name
FROM sys.views
WHERE name NOT LIKE 'sys.sp_%'
      AND name NOT LIKE 'xp_%'

OPEN viewCursor
FETCH NEXT FROM viewCursor
INTO @viewName

WHILE @@FETCH_STATUS = 0
BEGIN
    BEGIN TRY
        EXEC sp_refreshview @viewName
        PRINT 'Object ' + QUOTENAME(@viewName) + ' was successfully marked for recompilation.'
    END TRY
    BEGIN CATCH
    -- Handle any errors if needed
    END CATCH
    FETCH NEXT FROM viewCursor
    INTO @viewName
END

CLOSE viewCursor
DEALLOCATE viewCursor

DBCC FREEPROCCACHE;
Posted in Database, SQL | Leave a comment

Handling SQL stored procedures that return more than one selection in c#

Introduction

You can skip this shit if you just want the code…

How strange that after all these years, I constantly find myself doing things I’ve never done at my current employer. I did something like this years ago at another job, but that was a lifetime ago before my years of addiction and then these last ten years of sobriety, so I did not remember it at all.

Anyway, I was working on a couple of reports. I have a detail view, where I perform some calculations in a stored procedure. But I also have to show a summary report, which does some aggregation. Since the calculations were done in a stored proc rather than directly in a view, it takes some time. (Not much time. I’m deliberately testing with a whole month’s data and thousands of rows, while in production it will typically be run every other day.) So not a lot of time, but still… Why call the stored proc twice? What I did instead, is do two selections in the same stored procedure. I already have the calculated values in the scope of a temporary table which gets returned as the detail view – then I can aggregate, pivot, and whatever else I need for the summary, on the in memory table, and the summary will return basically instantly. That’s much better than running the original query twice.

The code

The problem for me was I didn’t know how to call a stored procedure that returns more than one selection of data with c#. Even controls like the built in SQLDataSource only know about the first set of results. But it turns out that reading multiple selections can be done easily with the SqlDataReader, which has a NextResult method.

I already use a reader in my helper class used for all my SQL tasks, so I just added a new couple of methods to it. I’ll show the code here, and include a link to download the whole helper class below. I haven’t made these new methods overloads, because I have a lot of existing production code in a couple of different systems using this class, so I can’t change the return type of existing methods without refactoring thousands of lines of code. So here I have two new methods (only implemented the synchronous version for now), which return a DataSet that can contain multiple DataTable instances. (My older methods all return a single DataTable directly.)

First of all, the methods all use this little extension so I’ll include it, otherwise the code won’t compile:

public static T[] ToArray<T>(this ICollection collection)
{
    var items = new T[collection.Count];
    collection.CopyTo(items, 0);
    return items;
}

My QueryHandler class has a ConnectionString class level property. That’s the only other thing missing if you want to use this code without bothering with the rest of the class. My new code for today’s post is just this:

public DataSet OpenMultiSelectQuery(string query, Dictionary<string, object> parameters, CommandType commandType = CommandType.Text)
{
    using (var connection = new SqlConnection(this.connectionString))
    {
        connection.Open();
        using (SqlCommand command = new SqlCommand(query, connection))
        {
            command.CommandTimeout = 0;
            command.CommandType = commandType;

            foreach (var kvp in parameters)
                command.Parameters.AddWithValue(kvp.Key, kvp.Value);

            using (var reader = command.ExecuteReader())
            {
                var dataSet = new DataSet();

                do
                {
                    var dataTable = new DataTable();
                    var schemaTable = reader.GetSchemaTable();

                    if (schemaTable != null)
                    {
                        foreach (DataRow row in schemaTable.Rows)
                            dataTable.Columns.Add((string)row["ColumnName"], Type.GetType(row["DataType"].ToString()));

                        var columns = dataTable.Columns.ToArray<DataColumn>();

                        while (reader.Read())
                            dataTable.Rows.Add(columns.Select(c => reader[c.Ordinal]).ToArray());
                    }

                    dataSet.Tables.Add(dataTable);
                } while (reader.NextResult());

                return dataSet;
            }
        }
    }
}

public DataSet OpenMultiSelectStoredProcedure(string procName, Dictionary<string, object> parameters)
{
    return OpenMultiSelectQuery(procName, parameters, CommandType.StoredProcedure);
}

Then my code that calls it looks something like this:

private void BindGrids()
{
    int edcID = (int)((ASPxListBox)ddeEDCs.FindControl("lsEDCs")).SelectedItem.Value;
    DateTime lastPaidDate = deStartDate.Date;

    var dataset = queryHandler.OpenMultiSelectStoredProcedure("spGetEdcDetails", new Dictionary<string, object> { { "@EdcID", edcID }, { "@LastPaidDate", lastPaidDate } });

    gvEdcDetails.DataSource = dataset.Tables[0];
    gvEdcDetails.DataBind();

    gvEdcSummary.DataSource = dataset.Tables[1];
    gvEdcSummary.DataBind();
}

Lastly, you can download the whole helper class here. There are some other new methods added to it since I last shared, but I may write about those in a future blog post.
QueryHandler.zip

Posted in Programming | Tagged , , | Leave a comment

A follow up to my CreateDataTable helper method as shared on 18th April

Just updating because I changed my helper method that was generated by ChatGPT, as shared in this post.

So it turns out that there is one place in production code that uses an in-memory table with a primary key. I managed to change the helper method I use for creating such tables such that it still works everywhere else too… Now the last optional parameter is set both to auto-incrementing and the table’s primary key(s). Normally you’ll either omit the last parameter or simply pass a string of “ID” to it, assuming that’s the name of your first column and an IDENTITY field. This version of the helper method works for all the cases I need, namely:

  1. Creating  a table to populate data where we don’t care if there is or isn’t a primary key.
  2. Creating a table which we will populate and pass to a bulk insert statement with auto-incrementing column(s).
  3. Creating a table where we need to explicitly set a primary key.

That’s all cases for me. The trick was to simply set all columns passed to the last parameter as both PK and auto-incrementing columns. This might need to be changed for your specific needs, but it always works for all the cases where I need it…

public static DataTable CreateDataTable(string tableName, Dictionary<string, Type> columns, params string[] primaryKeyColumns)
{
    var dataTable = new DataTable(tableName);

    foreach (var kvp in columns)
    {
        var columnName = kvp.Key;
        var columnType = kvp.Value;
        var dataColumn = new DataColumn(columnName, columnType);

        // Check if the column is a primary key column
        if (primaryKeyColumns != null && primaryKeyColumns.Contains(columnName))
        {
            dataColumn.Unique = true;
            dataColumn.AutoIncrement = true;
            dataColumn.AutoIncrementSeed = 1;
            dataColumn.AutoIncrementStep = 1;
        }
        dataTable.Columns.Add(dataColumn);
    }

    if (primaryKeyColumns != null && primaryKeyColumns.Length > 0)
    {
        var primaryKey = new DataColumn[primaryKeyColumns.Length];
        for (int i = 0; i < primaryKeyColumns.Length; i++)
        {
            primaryKey[i] = dataTable.Columns[primaryKeyColumns[i]];
        }
        dataTable.PrimaryKey = primaryKey;
    }

    return dataTable;
}

You’d call it like this, or leave out the last parameter if you don’t need a PK:

var table = QueryHandler.CreateDataTable("MyTable", new Dictionary<string, Type>
{
    {"ID", typeof(int)},
    {"Name", typeof(string)},
    {"Age", typeof(int)},
    {"Address", typeof(string)}
}, "ID");
Posted in Database, Programming | Tagged , | Leave a comment

More playing with ChatGPT… I asked it to use a params array and extend a method it wrote for me before

Recently I used ChatGPT to write a method for me to create an in memory Table, but it occurred to me that I might populate it using DataTable.LoadDataRow and I might sometimes want some columns to increment automatically. So I asked it to extend the method by adding a params parameter array so that it would not break my existing code.

Once again, it got this right and the new method doesn’t break my existing code…

My prompt: What would be the best way to extend this method to accept a list of column names that should be set to AutoIncrement = true? Possibly using the params keyword so that it can be the last parameter and may be omitted.

public static DataTable CreateDataTable(string tableName, Dictionary<string, Type> columns)
{
    var dataTable = new DataTable(tableName);

    foreach (var kvp in columns)
    {
        var columnName = kvp.Key;
        var columnType = kvp.Value;
        var dataColumn = new DataColumn(columnName, columnType);
        dataTable.Columns.Add(dataColumn);
    }

    return dataTable;
}

The answer:

One way to extend the CreateDataTable method to accept a list of column names that should be set to AutoIncrement = true is to add an optional params parameter of type string[] that represents the list of column names. We can modify the foreach loop to set the AutoIncrement property of the DataColumn object to true if the columnName matches any of the names in the params array.

Here’s an updated implementation of the CreateDataTable method with this modification:

public static DataTable CreateDataTable(string tableName, Dictionary<string, Type> columns, params string[] autoIncrementColumns) { var dataTable = new DataTable(tableName); foreach (var kvp in columns) { var columnName = kvp.Key; var columnType = kvp.Value; var dataColumn = new DataColumn(columnName, columnType); if (autoIncrementColumns != null && autoIncrementColumns.Contains(columnName)) { dataColumn.AutoIncrement = true; } dataTable.Columns.Add(dataColumn); } return dataTable; }

With this modification, you can specify the list of column names to set as AutoIncrement like this:

var columns = new Dictionary<string, Type> { {"ID", typeof(int)}, {"Name", typeof(string)}, {"Age", typeof(int)}, {"Address", typeof(string)} }; var table = CreateDataTable("MyTable", columns, "ID");

In this example, the ID column will be set as AutoIncrement = true, and the other columns will have their default AutoIncrement value of false. If you don’t want to set any columns as AutoIncrement, you can simply omit the params argument, like this:

var table = CreateDataTable("MyTable", columns);

Yep, that will do. Again this is a simple extension to my existing code (but Hey! All code should be simple. I’ve believed for years that if your code is complicated, it is probably wrong.), but I do like using the AI this way, to refactor code. It’s convenient that I told it exactly what I want in English and it implemented it as I would have.

We could of course use a similar technique to specify a single string parameter that we want set as the primary key, but instead of using the params keyword, just default the string to null. (Generally I don’t need to set a field as the PK, because telling it to auto-increment the ID which happens to be the PK achieves the same thing, and it can be used for complex queries that bulk insert to a temp table which is then passed to stored procs that join on that table, etc. So this little utility method is very handy to me.)

Posted in Programming | Tagged , , | Leave a comment

More playing with asking ChatGPT and extension methods

Again, a contrived example but a fun one. I have an extension method called “ForEachAsync”, which I downloaded from Microsoft sample code years ago. It allows you to process items in a collection asynchronously in parallel. It enumerates the collection and fires off up to a given number of tasks in parallel as it loops through the collection, and then returns once all of those tasks have completed.

I wrapped it with a static “ForAsync” method that uses Enumerable.Range internally, but I figured it would have been more logical (but less lazy) to have the ForAsync be an extension on IEnumrable<int> (that is, the return value of the range). It’s more concise, however also slightly more work as it would require the caller to create the range and then call the extension. So it isn’t necessarily better, but it is an extension method instead of a static one on some random internal class name.

So I asked ChatGPT to generate the extension method version of ForAsync based on the above criteria. But can it understand the request and write the extension method exactly as I would have? Of course the answer is “Yes.”.

My prompt: I wrote the ForASync method based on the given ForEachAsync. Would it be better to make ForAsync an extension on IEnumerable<int> instead? Can you generate an extension on IEnumerable<int> that wraps ForEachAsync instead?

/// <summary>An asynchronous ForAsync implementation.</summary>
/// <remarks>It simply creates an <b>Enumerable.Range</b> and wraps <b>ForEachAsync</b>.</remarks>
public static Task ForAsync(int fromInclusive, int toExclusive, int maxDegreeOfParallelism, Func<int, Task> body)
{
    return Enumerable.Range(
        fromInclusive, toExclusive).
        ForEachAsync(maxDegreeOfParallelism, async i => await body(i));
}

/// <summary>A ForEachAsync implementation. Based on a sample in an article by Stephen Toub,
/// <a href="http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx">
/// Implementing a simple ForEachAsync, part 2</a>.</summary>
public static Task ForEachAsync<T>(this IEnumerable<T> source,
    int maxDegreeOfParallelism,
    Func<T, Task> body)
{
    return Task.WhenAll(
        from partition in Partitioner.Create(source).GetPartitions(maxDegreeOfParallelism)
        select Task.Run(async () =>
        {
            using (partition)
                while (partition.MoveNext())
                    await body(partition.Current);
        }));
}

Its answer:

Yes, it would be better to make ForAsync an extension method on IEnumerable<int> instead of a static method. Here’s how you can do it:

public static async Task ForAsync(this IEnumerable<int> source, int maxDegreeOfParallelism, Func<int, Task> body) { await source.ForEachAsync(maxDegreeOfParallelism, async i => await body(i)); }

This code defines an extension method called ForAsync that extends IEnumerable<int>. It calls the ForEachAsync method on the source enumerable with the specified maxDegreeOfParallelism and the provided body delegate.

You can then use this extension method like this:

await Enumerable.Range(0, 100).ForAsync(4, async i => { // Do some work with i });

This will execute the provided delegate in parallel for each integer in the range from 0 to 99, with up to 4 tasks running in parallel at a time.


Again, is this not impressive? I love the way it explains the code.

In practice, depending on what your code does, this isn’t necessarily any better than creating a list of all your tasks and running all of them at the same time using Task.WhenAll, but… the maxDegreeOfParalleism  parameter is a useful throttle. I mean, letting all your tasks run in parallel is all good and well if there are hundreds of items to process, but probably very unwise if there are thousands or more. (I never use ForAsync, but I do use the ForEachAsync method a lot in production code, normally using a throttle of Environment.ProcessorCount.)

Also, you might ask: Why write ForAsync at all?
Well, you could call ForEachAsync on the range if you really wanted to, but I don’t find that intuitive. Sometimes I want to use the items in the collection by index. Maybe I need to map the results of whatever I do with the items back to the original, and I can use the index to do so; maybe I have a collection that isn’t thread-safe, but I’m not writing to it so it’s OK to read the items by index. Maybe I’m just lazy as fuck and I want to take some code from a for loop and copy it verbatim. But whatever the reason, it’s a nice extension to have.

Posted in Programming | Tagged , , , | Leave a comment

I asked ChatGPT to write a simple extension method for me to make Invoke checks easier and… Job well done!

OK, this is kind of contrived I suppose. I could have written this code myself, but I wanted to see if ChatGPT would get it right, and it did. This code works perfectly.

My prompt: Based on the two if statements below in Windows.Forms, I would like an Extension method on Control and an Action parameter, which then either calls the action via BeginInvoke or not if unnecessary, and give me an example replacing each if statement calling this extension method.

if (progressBar.InvokeRequired)
    progressBar.BeginInvoke(new Action(() => progressBar.Maximum = requests.Count));
else
    progressBar.Maximum = requests.Count;


if (progressBar.InvokeRequired)
    progressBar.BeginInvoke(new Action(() => progressBar.Value += 1));
else
    progressBar.Value += 1;

Its answer:

Here’s an extension method on Control that you can use to simplify these Invoke checks:

public static void InvokeIfNeeded(this Control control, Action action) { if (control.InvokeRequired) { control.BeginInvoke(action); } else { action(); } }

You can use this extension method to replace each of the Invoke checks in your code, like this:

// Replacing progressBar.Maximum = requests.Count progressBar.InvokeIfNeeded(() => progressBar.Maximum = requests.Count); // Replacing progressBar.Value += 1 progressBar.InvokeIfNeeded(() => progressBar.Value += 1);

This code calls the InvokeIfNeeded extension method on the progressBar control, passing in the Action that we want to execute. The extension method checks if Invoke is required by checking the InvokeRequired property of the control. If it is required, the Action is executed asynchronously using BeginInvoke. If it is not required, the Action is executed synchronously.


Well, colour me impressed! It’s a simple enough example, but I do find it much easier to use this extension than doing the check that it does all over the show.

I’m also really impressed that it named the method “InvokeIfNeeded”. I didn’t use the word “needed”. In fact my awkwardly phrased prompt used a double negative with “that calls the action via BeginInvoke or not if unnecessary”. Maybe I should have tried asking it directly for an extension method to simplify the Invoke check?

Posted in Programming | Tagged , , , , | Leave a comment

A helper method to create a table, given the table name and the column names/types, generated by ChatGPT

This is so cool. We have lots of methods that create in-memory tables all over the place in one codebase. I thought, why not have one reusable helper method to create them so it’s more convenient to do? And instead of writing it myself, let’s see what happens when I ask ChatGPT to write it for me…

My prompt: Generate a method called CreateDataTable that takes two parameters, string tableName, and Dictionary<string, Type> columns. Then change the supplied method to call the new one.

private DataTable SetupBatchPaymentTable()
{
    return new DataTable
    {
        TableName = "BatchPayment",
        Columns =
        {
            new DataColumn("ID", typeof(Int32)),
            new DataColumn("BatchID", typeof(Int32)),
            new DataColumn("PaymentID", typeof(Int32)),
            new DataColumn("DatasetNumber", typeof(Int64)),
            new DataColumn("SequenceNumber", typeof(Int64)),
            new DataColumn("UserCreated", typeof(Int32)),
            new DataColumn("DateCreated", typeof(DateTime)),
        }
    };
}

And its answer:

Here’s the implementation of the CreateDataTable method that you requested:

public DataTable CreateDataTable(string tableName, Dictionary<string, Type> columns)
{
    var dataTable = new DataTable(tableName);

    foreach (var kvp in columns)
    {
        var columnName = kvp.Key;
        var columnType = kvp.Value;
        var dataColumn = new DataColumn(columnName, columnType);
        dataTable.Columns.Add(dataColumn);
    }

    return dataTable;
}

The CreateDataTable method takes two parameters: tableName and columns. The tableName parameter is the name of the table, and the columns parameter is a dictionary that contains the name and data type of each column.

To use this method in the SetupBatchPaymentTable method, you can replace the code with the following:

var columns = new Dictionary<string, Type>
{
    { "ID", typeof(int) },
    { "BatchID", typeof(int) },
    { "DatasetNumber", typeof(long) },
    { "SequenceNumber", typeof(long) },
    { "UserCreated", typeof(int) },
    { "DateCreated", typeof(DateTime) }
};

return CreateDataTable("BatchPayment", columns);

This code creates a dictionary with the name and data type of each column, then calls the CreateDataTable method to create the DataTable.


Wow, hey? The only thing I will typically do differently is pass the second parameter, the dictionary, using the collection initializer inline, as in:

private DataTable SetupBatchPaymentTable()
{
    return QueryHandler.CreateDataTable("BatchPayment", new Dictionary<string, Type>
    {
        { "ID", typeof(int) },
        { "BatchID", typeof(int) },
        { "PaymentID", typeof(int) },
        { "DatasetNumber", typeof(long) },
        { "SequenceNumber", typeof(long) },
        { "UserCreated", typeof(int) },
        { "DateCreated", typeof(DateTime) }
    });
}

…where I’ve added this new helper method to my general purpose data helping class called QueryHandler. This is the first time I’ve used ChatGPT to generate a method for me; it probably won’t be the last. It’s a trivial helper method really, but still, as I wrote before, why write it myself if a free tool can do the job for me?

Update: My question asked for a Dictionary<string, object> originally, but the generated code gave me type mismatch errors when passing the data tables to bulk inserts, because the unboxing got the type as “RunTimeType” instead of the correct type. I have amended this post to use Dictionary<string, Type> instead.

Posted in Programming | Tagged , , | Leave a comment

Beware, sometimes ChapGPT makes some interesting mistakes

After yesterday’s post, I figured it would be a good idea to post the one caveat I’ve found with the AI’s responses. Sometimes it makes interesting little mistakes, so you’d better test any code it gives you carefully, or as in the case of my examples, sometimes the issues are obvious enough. But do be careful.

I’ve had this happen a few times, but for this post I’ll just give two examples… ChatGPT will merrily take code that you ask it to optimize, when you’re doing something inherently not thread safe in a loop, and recommend you process the loop in parallel, without warning you about the non threadsafe operation that will then break. For example, say your loop adds a row to a table, or an item to a collection. Ask it about the line that will fail a second time, and it will update the code to use a threadsafe collection such as a ConcurrentBag<T>.

In the case below, I asked it to change some code that uses string concatenation for a SQL query to update the code to use parameters. (Yes, I’m being lazy. Why refactor it myself when I now have this tool to do it for me?)

My query: change the 2 values concatenated in the sql script into parameters

foreach (DispatchPaymentStatus PaymentStatus in PaymentStatuses)
{
    string QueryStatus = String.IsNullOrEmpty(Status) ? "NULL" : "'" + Status + "'";

    Command.CommandText = "UPDATE DispatchPaymentStatus SET DispatchStatus = " + QueryStatus + ", DateModified = GETDATE() WHERE ID = " + PaymentStatus.ID + ";";
    await Command.ExecuteNonQueryAsync();
}

Its answer:

To change the two values concatenated in the SQL script into parameters, you can modify the SQL query to use parameterized queries. Here’s an updated version of the code that uses parameters:

foreach (DispatchPaymentStatus PaymentStatus in PaymentStatuses) { string queryStatus = string.IsNullOrEmpty(Status) ? null : Status; Command.CommandText = "UPDATE DispatchPaymentStatus SET DispatchStatus = @Status, DateModified = GETDATE() WHERE ID = @ID;"; Command.Parameters.Clear(); Command.Parameters.AddWithValue("@Status", queryStatus); Command.Parameters.AddWithValue("@ID", PaymentStatus.ID); await Command.ExecuteNonQueryAsync(); }

In the above code, we’ve replaced the two concatenated values with parameters, @Status and @ID. We’ve also used Command.Parameters.AddWithValue() method to add values to the parameters. This method helps to avoid SQL injection attacks and improves performance by allowing SQL Server to reuse the execution plan for the query.


Can you see the problem with that answer?

  • If the status parameter is null and you pass null with AddWithValue, SQL will treat it the same as if you had not included that parameter at all, with an error something like “The parameterized query blablabla expects the parameter @Status which was not supplied”.
  • You need to explicitly pass it DBNull instead.

Asking ChatGPT by spelling out the error prompts it to give me a better answer.

My second prompt: The above will fail on the update if status is null. Should cast to DBNull when adding the parameter.

And this time the answer is much better:

You’re correct, when using parameters, passing a null value directly to a parameter will not work as expected, and the query will fail. To handle this situation, we need to use the DBNull.Value instead of passing a null value. Here’s the updated code with the required changes:

foreach (DispatchPaymentStatus PaymentStatus in PaymentStatuses) { string queryStatus = string.IsNullOrEmpty(Status) ? null : Status; Command.CommandText = "UPDATE DispatchPaymentStatus SET DispatchStatus = @Status, DateModified = GETDATE() WHERE ID = @ID;"; Command.Parameters.Clear(); Command.Parameters.AddWithValue("@ID", PaymentStatus.ID); if (!string.IsNullOrEmpty(queryStatus)) { Command.Parameters.AddWithValue("@Status", queryStatus); } else { Command.Parameters.AddWithValue("@Status", DBNull.Value); } await Command.ExecuteNonQueryAsync(); }

In the above code, we’ve used the DBNull.Value when queryStatus is null or empty, and we’ve also modified the order of adding parameters. We first add @ID parameter and then add the @Status parameter. This is to ensure that we always have the @ID parameter even when @Status is null.


I’ve had this sort of issue several times now. It is still a great tool to use, but these sorts of answers are better suited for intermediate to senior developers. Beginners might not see the errors in its generated code.

Posted in Programming | Tagged , | Leave a comment

More playing with ChatGPT and Linq

I just can’t get enough of playing around with ChatGPT. It really is amazing, and scary! This is not far from replacing us all.

In this case, I asked it to flatten two loops using Linq. It gives code that looks right but can’t work, because it’s inner loop no longer has access to a variable defined in the outer loop. But a second prompt fixed it. Here, let me show you:

My prompt: Can I use Linq to flatten these loops?

// Insert the instruction payment batch records.
foreach (TransactionRequestDataset dataset in batch.RequestDatasets.OfType<TransactionRequestDataset>())
{
    foreach (var request in dataset.Requests)
    {
        var outgoingTransaction = (TransactionRequest)request;
        // Add the records to insert into instruction payment.
        AddInstructionPaymentRecord(outgoingTransaction, instructionPaymentTable);

        // Add the records to insert into batch instruction payment.
        AddBatchLinkRecord(batch, dataset, outgoingTransaction, batchInstructionPaymentTable);

        // Record the id so that it can be deleted later.
        AddIDRecord(outgoingTransaction, instructionPendingPaymentsToDeleteTable);
    }
}

It’s answer:

Yes, you can use LINQ to flatten the loops in the provided code. Here’s an example of how you can use LINQ to achieve this:


batch.RequestDatasets.OfType<TransactionRequestDataset>()
     .SelectMany(dataset => dataset.Requests.OfType<TransactionRequest>())
     .ToList()
     .ForEach(outgoingTransaction =>
     {
        AddInstructionPaymentRecord(outgoingTransaction, instructionPaymentTable);
        AddBatchLinkRecord(batch, dataset, outgoingTransaction, batchInstructionPaymentTable);
        AddIDRecord(outgoingTransaction, instructionPendingPaymentsToDeleteTable);
     });

In this code, the OfType LINQ method filters the request datasets to only include TransactionRequestDataset instances. Then, the SelectMany method is used to flatten the nested loops into a single sequence of TransactionRequest instances. Finally, the ForEach method is used to iterate over the sequence and execute the required logic for each TransactionRequest.


Can you see the problem with its answer above?

  • In the generated Linq ForEach method, the second line calls AddBatchLinkRecord, with a parameter of dataset. But dataset is inaccessible in this scope. This code will not compile.

So I prompted ChatGPT again, using its neat feature where it recalls the previous questions and answer:

My second prompt: Previous example can’t work because dataset is inacessible in the foreach. How can we improve this?

And this time, its answer is perfect:

You’re right, I apologize for the mistake in my previous example. Since dataset is defined in the outer loop, it is not accessible in the inner loop. To address this, we can use a nested from clause in the LINQ query to create a new scope for dataset:


var transactionRequests = from dataset in batch.RequestDatasets.OfType<TransactionRequestDataset>()
                          from request in dataset.Requests.OfType<TransactionRequest>()
                          select new { Dataset = dataset, Request = request };

foreach (var transactionRequest in transactionRequests)
{
    var dataset = transactionRequest.Dataset;
    var outgoingTransaction = transactionRequest.Request;
    // Add the records to insert into instruction payment.
    AddInstructionPaymentRecord(outgoingTransaction, instructionPaymentTable);

    // Add the records to insert into batch instruction payment.
    AddBatchLinkRecord(batch, dataset, outgoingTransaction, batchInstructionPaymentTable);

    // Record the id so that it can be deleted later.
    AddIDRecord(outgoingTransaction, instructionPendingPaymentsToDeleteTable);
}

In this code, the from clause is used twice to iterate over both the TransactionRequestDataset instances and the requests within them. The select clause is used to create an anonymous type that combines the Dataset and Request objects into a single object that is easier to work with in the subsequent foreach loop.

Then, in the foreach loop, we can extract the Dataset and Request objects from the anonymous type and use them to execute the required logic for each TransactionRequest.


Notice in its second answer, it even put back the comments that were in my original prompt, although it omitted them in its first answer. Is this not fucking amazing? Note that the answer was actually not to flatten both loops. Instead it captured the variable that had gone out of scope, along with the request, and then looped over an anonymous type with both variables in scope. And if you think this isn’t going to replace us all… note that this is the kind of question I’d ask in programming assessments were I involved with recruitment… and I can guarantee, at least around here (South Africa), that the vast majority of job applicants would get this wrong.

Posted in Programming | Tagged , , | Leave a comment

A script to truncate all your SQL server logs

First of all, don’t run this script in production unless you really know it’s safe to truncate your transactional logs.

So, I have this script I use to truncate my transactional logs down to 1MB – I normally use it on our dev SQL Server to free up space so that we can restore a backup from production, for debugging, and also to test other scripts and development changes against a database that’s as close as possible to production. I run this script because these logs grow, sometimes up to hundreds of Gigabytes in size, and then we don’t have enough drive space to restore other database backups. We also don’t do transactional backups in our dev environment.

My script only truncated the log on one database, so I took it and asked ChatGPT how to add a cursor and loop through all the databases. I had to tweak the script a bit – the AI generated one with blank strings for the file name, but prompting it a second time to use the technique from my first script gave me one that works well.

As suggested, don’t run this in production unless you really want to and you know the risks. Enjoy!

DECLARE @DBName NVARCHAR(500)
DECLARE @logfilename NVARCHAR(255)
DECLARE @SQL NVARCHAR(MAX)

DECLARE db_cursor CURSOR FOR
SELECT name
FROM master.dbo.sysdatabases
WHERE name NOT IN ( 'master', 'model', 'msdb', 'tempdb' )

OPEN db_cursor
FETCH NEXT FROM db_cursor
INTO @DBName

WHILE @@FETCH_STATUS = 0
BEGIN
    SET @logfilename =
    (
        SELECT name
        FROM sys.master_files
        WHERE database_id = db_id(@DBName)
              AND type = 1
    );

    SET @SQL
        = N'USE ' + QUOTENAME(@DBName) + ';
            ALTER DATABASE ' + QUOTENAME(@DBName)
          + ' SET RECOVERY SIMPLE;
            CHECKPOINT;
            DBCC SHRINKFILE (''' + @logfilename + ''', 1);
            ALTER DATABASE ' + QUOTENAME(@DBName) + ' SET RECOVERY FULL;'
    EXEC sp_executesql @SQL

    FETCH NEXT FROM db_cursor
    INTO @DBName
END

CLOSE db_cursor
DEALLOCATE db_cursor



Posted in SQL | Tagged | Leave a comment

async implementations of File.ReadAllBytes, File.ReadAllLines, File.ReadAllText, File.WriteAllBytes, File.WriteAllLines, File.WriteAllText, and File.AppendAllLines

OK, so I just happened to notice that somebody else at my job used my FileAsync.WriteAllLinesAsync method. So it’s running in production. I guess that’s what happens when you add some experimental code to the codebase without telling anyone not to use it? Might as well share it then, huh?

So, a while back I wrote my own versions of all the File.ReadAll… and File.WriteAll… methods. Originally they used my own extension method for copying stream to stream as outlined in this old blog post, but at some point I changed them to use the built in Stream.CopyToAsync method via a MemoryStream. So that’s what all the code does internally; it just wraps the file stream with one in memory and copies one stream to another asynchronously. That’s what all these methods do.

For example if we take just a couple of them to see the pattern, it is this:

public static async Task WriteAllBytesAsync(string path, byte[] bytes, int bufferSize)
{
    using (var memoryStream = new MemoryStream(bytes))
    {
        using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, true))
        {
            await memoryStream.CopyToAsync(stream, bufferSize);
        }
    }
}

public static async Task WriteAllLinesAsync(string path, IEnumerable<string> contents)
{
    await WriteAllLinesAsync(path, contents, Encoding.UTF8);
}

public static async Task WriteAllLinesAsync(string path, IEnumerable<string> contents, Encoding encoding)
{
    using (var memoryStream = new MemoryStream(contents.SelectMany(s => encoding.GetBytes(s.EndsWith("\r\n") ? s : s.TrimEnd() + "\r\n")).ToArray()))
    {
        using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, BufferSize, true))
        {
            await memoryStream.CopyToAsync(stream, BufferSize);
        }
    }
}

And the whole class is this:

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Romy.Core
{
    public static class FileAsync
    {
        #region Public Fields

        public const int BufferSize = 0x2000;

        #endregion Public Fields

        #region Private Fields

        private static readonly byte[] NoBytes = Enumerable.Empty<byte>().ToArray();

        #endregion Private Fields

        #region Public Methods

        public static Task AppendAllBytesAsync(string path, byte[] bytes)
        {
            return AppendAllBytesAsync(path, bytes, BufferSize);
        }

        public static async Task AppendAllBytesAsync(string path, byte[] bytes, int bufferSize)
        {
            using (var memoryStream = new MemoryStream(bytes))
            {
                using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.None, bufferSize, true))
                {
                    await memoryStream.CopyToAsync(stream, bufferSize);
                }
            }
        }

        public static async Task AppendAllLinesAsync(string path, IEnumerable<string> contents)
        {
            var lines = File.Exists(path) ? new List<string>(await ReadAllLinesAsync(path)) : new List<string>();
            lines.AddRange(contents);

            await WriteAllLinesAsync(path, lines.ToArray());
        }

        public static Task<byte[]> ReadAllBytesAsync(string filename)
        {
            return ReadAllBytesAsync(filename, BufferSize);
        }

        public static async Task<byte[]> ReadAllBytesAsync(string filename, int bufferSize)
        {
            try
            {
                var length = (int)new FileInfo(filename).Length;
                byte[] bytes = NoBytes;

                using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, true))
                {
                    using (var memoryStream = new MemoryStream(length))
                    {
                        await stream.CopyToAsync(memoryStream, bufferSize);
                        bytes = memoryStream.ToArray();
                    }
                }
                return bytes;
            }
            catch { return NoBytes; }
        }

        public static async Task<string[]> ReadAllLinesAsync(string path)
        {
            var lines = new List<string>();
            var line = string.Empty;

            using (var reader = new StreamReader(path, true))
            {
                while ((line = await reader.ReadLineAsync()) != null)
                    lines.Add(line);
            }

            return lines.ToArray();
        }

        public static async Task<string> ReadAllTextAsync(string path)
        {
            var bytes = await ReadAllBytesAsync(path);
            return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
        }

        public static async Task WriteAllBytesAsync(string path, byte[] bytes)
        {
            using (var memoryStream = new MemoryStream(bytes))
            {
                using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, BufferSize, true))
                {
                    await memoryStream.CopyToAsync(stream, BufferSize);
                }
            }
        }

        public static async Task WriteAllBytesAsync(string path, byte[] bytes, int bufferSize)
        {
            using (var memoryStream = new MemoryStream(bytes))
            {
                using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, true))
                {
                    await memoryStream.CopyToAsync(stream, bufferSize);
                }
            }
        }

        public static async Task WriteAllLinesAsync(string path, IEnumerable<string> contents)
        {
            await WriteAllLinesAsync(path, contents, Encoding.UTF8);
        }

        public static async Task WriteAllLinesAsync(string path, IEnumerable<string> contents, Encoding encoding)
        {
            using (var memoryStream = new MemoryStream(contents.SelectMany(s => encoding.GetBytes(s.EndsWith("\r\n") ? s : s.TrimEnd() + "\r\n")).ToArray()))
            {
                using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, BufferSize, true))
                {
                    await memoryStream.CopyToAsync(stream, BufferSize);
                }
            }
        }

        public static async Task WriteAllTextAsync(string path, string contents)
        {
            await WriteAllLinesAsync(path, new[] { contents });
        }

        #endregion Public Methods
    }
}
Posted in General, Programming | Tagged , , , , , , | Leave a comment

A fine example of ChatGPT understanding c# Linq

I can’t get enough of using ChatGPT… Check out this request… I give it the relevant code that I want to improve and it understands the code well enough. It does a good job with SQL queries too, by the way.

show me a shorter way of getting these instruction payment IDs to a list

            List<int> mandateOperationIds = new List<int>();

            List<IncomingBatchContext> responseBatches = incomingResponseBatches.Where(rb => rb.Batch.ResponseDatasets.Any(d => d is MandateResponseDataset)).ToList();

            if (responseBatches.Count > 0)
            {
                foreach (IncomingBatchContext incomingResponseBatch in responseBatches.Where(b => b.Batch.ParentID.HasValue))
                {
                    foreach (ResponseDataset dataset in incomingResponseBatch.Batch.ResponseDatasets)
                    {
                        if (dataset is MandateResponseDataset)
                        {
                            foreach (var response in dataset.Responses)
                            {
                                var mandateResponse = (MandateResponse)response;
                                mandateOperationIds.Add(mandateResponse.SystemID);
                            }
                        }
                    }
                }
            }

The result:

Certainly! Here’s a shorter way of getting the instructionPaymentIds:

List<int> instructionPaymentIds = incomingResponseBatches .Where(rb => rb.Batch.ResponseDatasets .Any(d => d is TransactionResponseDataset)) .Where(rb => rb.Batch.ParentID.HasValue) .SelectMany(rb => rb.Batch.ResponseDatasets .Where(d => d is TransactionResponseDataset) .SelectMany(d => ((TransactionResponseDataset)d).Responses) .OfType<TransactionResponse>() .Select(r => r.SystemID)) .ToList();

Explanation:

  • First, we filter incomingResponseBatches to only include batches that contain at least one TransactionResponseDataset.
  • Then, we filter the response batches again to only include those with a non-null ParentID.
  • Next, we use SelectMany to flatten out the nested loops and extract all TransactionResponse objects from the ResponseDatasets.
  • Finally, we extract the SystemID of each TransactionResponse and convert the results to a list using ToList().
Posted in Programming | Tagged , , | Leave a comment

ChatGPT is pretty fucking amazing

Check this one out! A simple problem that’s part of a much bigger solution. Giving little problems like this to ChatGPT is now my default. Seriously, fuck the toxic site that StackOverflow has become. Once again, my question in a blockquote and then the answer that it generated. This time it needs no clarification – for small problems like this it gets them 100% correct.

javascript parse a date from a string in format “dd-MM-yyyy”

var dateString = "16-03-2023";
var dateParts = dateString.split("-");
var dateObject = new Date(+dateParts[2], dateParts[1] - 1, +dateParts[0]);

console.log(dateObject);

To parse a date from a string in the format “dd-MM-yyyy” using JavaScript, you can use the Date constructor and the split method to extract the day, month, and year values from the string:

In this code, dateString contains the date string in the “dd-MM-yyyy” format. dateParts is an array containing the day, month, and year values, extracted using the split method with the “-” separator.

The new Date() constructor creates a new Date object with the year, month, and day values passed as arguments. The year is extracted from the third element of the dateParts array, and the month and day values are extracted from the first and second elements, respectively. Note that the month parameter of the Date constructor is zero-indexed, so we subtract 1 from the month value.

Posted in General, Programming, Work | Tagged , | Leave a comment

More playing with ChatGPT. Sometimes it gets stuff quite wrong, but the results are still useful

I’m moved from StackOverflow to using ChatGPT by default when I need help with small programming problems, as I did in my previous post. But sometimes it gets them weirdly wrong.

Here’s an example where the answer was quite wrong, but still useful to me… My question to ChatGPT was this:

devexpress version 13.1 initialize GridViewDataComboBoxColumn in aspxgridview with query based on another column value on data binding

And it’s answer, which I’ll once again end with a horizontal rule, was the following:

To initialize a GridViewDataComboBoxColumn in ASPxGridView with a query based on another column value during data binding, you can use the GridViewDataComboBoxColumn’s PropertiesComboBox.DataBound event.

Here’s an example:

C#

protected void GridView1_CustomColumnDisplayText(object sender, DevExpress.Web.ASPxGridViewColumnDisplayTextEventArgs e) { if (e.Column.FieldName == "MyComboBoxColumn") { ASPxComboBox comboBox = e.Value as ASPxComboBox; if (comboBox != null) { object myOtherColumnValue = GridView1.GetRowValues(e.VisibleRowIndex, "MyOtherColumn"); // Use myOtherColumnValue to query for the ComboBox's items comboBox.DataSource = // your data source comboBox.DataBind(); } } }

In the example code above, the CustomColumnDisplayText event is used to handle the initialization of the ComboBox column. The “MyComboBoxColumn” column field name is checked and an ASPxComboBox object is retrieved from the cell value. Then, the value of the “MyOtherColumn” column is retrieved for the same row and used to query the ComboBox’s items. Finally, the ComboBox’s DataSource property is set and the ComboBox is data-bound.

You can also set the PropertiesComboBox.DataSource property in the GridViewDataComboBoxColumn’s PropertiesComboBox.Init event handler, which fires when the ComboBox is created. For example:

C#

protected void MyComboBox_Init(object sender, EventArgs e) { ASPxComboBox comboBox = sender as ASPxComboBox; if (comboBox != null) { object myOtherColumnValue = GridView1.GetRowValues(comboBox.VisibleIndex, "MyOtherColumn"); // Use myOtherColumnValue to query for the ComboBox's items comboBox.DataSource = // your data source comboBox.DataBind(); } }

In this code, the GridViewDataComboBoxColumn’s PropertiesComboBox.Init event handler retrieves the ASPxComboBox object and sets its DataSource property based on the value of the “MyOtherColumn” column for the same row.


OK, so the second answer is completely wrong, but before analysing the results, allow me to explain what I’m doing here… I am doing something a little convoluted I guess… I have a grid bound to some data, but two fields in the grid are IDs which are lookups to other tables. Now I could join on those tables in my query and display the looked up values, but that would leave me unable to ask ChatGPT this somewhat complicated contrived question…

Normally the purpose of the combo box lookup fields is to do what that description implies… when the user puts the grid row into edit mode, they can select the value from a list. But as a side effect it also displays the looked up value, rather than the ID, in the grid the rest of the time. So even though this grid isn’t editable now, the lookups are useful. And maybe we’ll get a requirement to make the grid editable in future – in which case it will “just work”.

Also, I have combo boxes elsewhere on a popup control, so a user can do something with those values after clicking on a row in the grid. So… when the row is displayed, I want the second field to be looked up. (I’m gonna save those datasets and rebind them if the user clicks a button, because this data is needed for another action elsewhere.) I’d prefer it to be looked up via the value of the first field (because that field is a service type and is the parent type of a bunch of categories in the database) so that’s why I asked that particular question.

This is actually my follow up question – the event it gives me in the 2nd option doesn’t even exist in the given version of devexpress, so that answer is useless.

Now onto the first answer… This:

protected void GridView1_CustomColumnDisplayText(object sender, DevExpress.Web.ASPxGridViewColumnDisplayTextEventArgs e)
{
    if (e.Column.FieldName == "MyComboBoxColumn")
    {
        ASPxComboBox comboBox = e.Value as ASPxComboBox;
        if (comboBox != null)
        {
            object myOtherColumnValue = GridView1.GetRowValues(e.VisibleRowIndex, "MyOtherColumn");
            // Use myOtherColumnValue to query for the ComboBox's items
            comboBox.DataSource = // your data source
            comboBox.DataBind();
        }
    }
}

Recalling that my question was, “devexpress version 13.1 initialize GridViewDataComboBoxColumn in aspxgridview with query based on another column value on data binding”… Firstly:

  1. That’s not the data binding event. Instead, it’s an event that fires per row for a grid, allowing for custom display text. But the description given claimed it was the DataBound event.
  2. This is the important one… e.Value is the value displayed in the column. In my case it’s 24. That’s an ID, not a combo box. A junior developer might not know what to do with this information.

My actual code was then based on this event, but it was this:

image

So, it’s not my preferred way… This dataset will only give me a single value, not all the related values I’d have used in a combo box. But I can still use it elsewhere. My other combo box will use this value in a different query. But still, ChapGPT gave a mostly wrong answer. It’s still a bit far off from replacing us… a bit.

It is a useful tool for developers though. You can tweak the results by telling it what event you’d like to use, for example, or in this case, confuse it into giving you code for one event while describing another. It does often give very useful snippets of source code, but they seem to be more useful to us when we know what we are looking for. And it’s very useful when you use it to zoom in on solving problems specific to your needs. Just give it the right context…

Posted in General, Programming | Tagged , | Leave a comment

How to concatenate one string from multiple rows into a single string in SQL Server

Well, fuck me! I didn’t know how to do this so I asked ChatGPT. And it turns out there is a SQL function I never heard of, introduced in SQL Server 2017. I had to fiddle around with the phrasing of my question before it gave me this answer, but in the end, my search was based on:

microsoft sql multiple rows into one string

It spat out this:

In Microsoft SQL Server, you can use the STRING_AGG function to concatenate values from multiple rows into a single string. Here is an example query:

SELECT STRING_AGG(order_id, ', ') AS orders FROM orders;

In this example, we are selecting the order_id field and using the STRING_AGG function to concatenate the values into a single string, with a comma and space separator.

The output of this query will be a single row with a comma-separated list of all the order IDs from the orders table.

Note that the STRING_AGG function is available in SQL Server 2017 and later versions. If you are using an earlier version of SQL Server, you can use the FOR XML PATH method mentioned in my previous answer.


Pretty neat, huh? In my query, that allowed me to join on this and thus select the messages as a single field:

image

Note that without the cast to nvarchar(max), this resulted in the error:

STRING_AGG aggregation result exceeded the limit of 8000 bytes. Use LOB types to avoid result truncation.

Apparently SQL uses the type of the argument passed to it to determine the type it will output, so you might need the cast as I did to force it to use a large enough type for the resultant. I don’t know how many rows I’m concatenating… it could be a few.

Posted in Database, SQL | Tagged , , | Leave a comment

Merge PDF files in c# using iTextSharp

A while back I needed to have some code return between one and three PDF files in a viewer control, and a download page. It occurred to me that it would make more sense to merge them into a single PDF rather than figure out how to show them in three separate tabs  (or something, or even think too much about the presentation), and likewise for the download… instead of putting three documents into a zip file, merge them into one and let the user’s browser display them in a tab organically.

The code to do this is quite simple… get the latest version of iTextSharp, which is now a Nuget package, unlike the last time I used it way back in 2011.

My extension method to merge the documents, based on an example I found on StackOverflow using TextSharp, is just this:
(Actually I can’t find the original example I used. The code I see on StackOverflow now is way overcomplicated so I won’t link to it. Sadly that site is not as useful as it used to be. Come to think of it, it might not have been StackOverflow. My example is based on the Java version, which thankfully has been ported to c# so the code is almost identical in both languages.)

using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Collections.Generic;
using System.IO;

namespace Sample
{
    public static class PdfExtensions
    {
        public static byte[] MergePdfContentBytes(this IEnumerable<byte[]> pdfByteContent)
        {
            using (var stream = new MemoryStream())
            using (var document = new Document())
            using (var copy = new PdfSmartCopy(document, stream))
            {
                document.Open();

                foreach (var pdfBytes in pdfByteContent)
                {
                    using (var reader = new PdfReader(pdfBytes))
                        copy.AddDocument(reader);
                }
                document.Close();
                return stream.ToArray();
            }
        }
    }
}

Using it is easy enough… I can’t show you the code that I use to get PDF files… sometimes it’s from a database, sometimes a report that renders to PDF, and sometimes a SSRS control… But you can always find a way to get the PDF as a stream of bytes, then add them to a list. So using the extension method is as easy as this:

Response.ContentType = "application/pdf";
List<byte[]> list = new List<byte[]>();
// Add your pdf files to the list here...

Response.BinaryWrite(list.MergePdfContentBytes());
Response.End();
Posted in Programming | Tagged , , | Leave a comment

My index maintenance and log truncation script for SQL server

We had an issue a while back where we ran out of space for our SQL backup files, because the transactions logs were huge, and I mean they were hundreds of Gigabytes of data we never use. So if, like us, you don’t care about your logs, you can use this script to truncate them, as well as improve your indexes and query plans.

First of all, I use a brilliant solution written by someone else. Go here and download his MaintenanceSolution.sql. Then simply run it in SQL management Studio. His page documents exactly how to use it. My script simply calls his main stored procedure and also does some other stuff.

My script does the following:

  1. Truncate the transaction log down to 1MB.
  2. Runs a script I downloaded from a Microsoft example a while back, which defragments the indexes.
  3. Runs the downloaded maintenance stored proc to optimize the indexes. (Yes, doing both step 2 and 3 is overkill, I know).
  4. Note that I used to run sp_updatestats after for improved query plans, but now his solution does that already, so I commented it out.

We regularly had an issue here where a query would time out, and fixing the indexes as well as sp_updatestats would fix it. Now this does. I run it in a SQL job regularly and we haven’t had any more problems.

I’m actually putting this post of for myself, so I can easily download the solution in future. (Note that the deliberate overkill of both defragmenting then optimizing the indexes will grow the log again. That’s OK; it shouldn’t be too small – just need it to be smaller for backups.)

To use this, first install the referenced maintenance solution, then change “YourDatabaseName” to your database name…

-- DB Maintenance script

-- Truncate the log
USE YourDatabaseName;
GO
-- Truncate the log by changing the database recovery model to SIMPLE.
ALTER DATABASE YourDatabaseName SET RECOVERY SIMPLE;
GO
-- Shrink the truncated log file to 1 MB.
declare @logfilename varchar(255) = (
                                        SELECT name
                                        FROM sys.master_files
                                        WHERE database_id = db_id()
                                              AND type = 1
                                    )
DBCC SHRINKFILE(@logfilename, 1);
GO
-- Reset the database recovery model.
ALTER DATABASE YourDatabaseName SET RECOVERY FULL;
GO

-- Defrag the indexes/*Perform a 'USE <database name>' to select the database in which to run the script.*/  
-- Declare variables  
SET NOCOUNT ON;
DECLARE @tablename varchar(255);
DECLARE @execstr varchar(400);
DECLARE @objectid int;
DECLARE @indexid int;
DECLARE @frag decimal;
DECLARE @maxfrag decimal;

-- Decide on the maximum fragmentation to allow for.  
SELECT @maxfrag = 30.0;

-- Declare a cursor.  
DECLARE tables CURSOR FOR
SELECT TABLE_SCHEMA + '.' + TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE';

-- Create the table.  
CREATE TABLE #fraglist
(
    ObjectName char(255),
    ObjectId int,
    IndexName char(255),
    IndexId int,
    Lvl int,
    CountPages int,
    CountRows int,
    MinRecSize int,
    MaxRecSize int,
    AvgRecSize int,
    ForRecCount int,
    Extents int,
    ExtentSwitches int,
    AvgFreeBytes int,
    AvgPageDensity int,
    ScanDensity decimal,
    BestCount int,
    ActualCount int,
    LogicalFrag decimal,
    ExtentFrag decimal
);

-- Open the cursor.  
OPEN tables;

-- Loop through all the tables in the database.  
FETCH NEXT FROM tables
INTO @tablename;

WHILE @@FETCH_STATUS = 0
BEGIN
    -- Do the showcontig of all indexes of the table  
    INSERT INTO #fraglist
    EXEC ('DBCC SHOWCONTIG (''' + @tablename + ''')   
      WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS');
    FETCH NEXT FROM tables
    INTO @tablename;
END;

-- Close and deallocate the cursor.  
CLOSE tables;
DEALLOCATE tables;

-- Declare the cursor for the list of indexes to be defragged.  
DECLARE indexes CURSOR FOR
SELECT ObjectName,
       ObjectId,
       IndexId,
       LogicalFrag
FROM #fraglist
WHERE LogicalFrag >= @maxfrag
      AND INDEXPROPERTY(ObjectId, IndexName, 'IndexDepth') > 0;

-- Open the cursor.  
OPEN indexes;

-- Loop through the indexes.  
FETCH NEXT FROM indexes
INTO @tablename,
     @objectid,
     @indexid,
     @frag;

WHILE @@FETCH_STATUS = 0
BEGIN
    PRINT 'Executing DBCC INDEXDEFRAG (0, ' + RTRIM(@tablename) + ',  
      '   + RTRIM(@indexid) + ') - fragmentation currently ' + RTRIM(CONVERT(varchar(15), @frag)) + '%';
    SELECT @execstr = 'DBCC INDEXDEFRAG (0, ' + RTRIM(@objectid) + ',  
       '              + RTRIM(@indexid) + ')';
    EXEC (@execstr);

    FETCH NEXT FROM indexes
    INTO @tablename,
         @objectid,
         @indexid,
         @frag;
END;

-- Close and deallocate the cursor.  
CLOSE indexes;
DEALLOCATE indexes;

-- Delete the temporary table.  
DROP TABLE #fraglist;
GO

-- Optimize indexes
use master

go

EXECUTE dbo.IndexOptimize @Databases = 'YourDatabaseName',
                          @FragmentationLow = NULL,
                          @FragmentationMedium = 'INDEX_REORGANIZE,INDEX_REBUILD_ONLINE,INDEX_REBUILD_OFFLINE',
                          @FragmentationHigh = 'INDEX_REBUILD_ONLINE,INDEX_REBUILD_OFFLINE',
                          @FragmentationLevel1 = 5,
                          @FragmentationLevel2 = 30,
                          @UpdateStatistics = 'ALL',
                          @OnlyModifiedStatistics = 'Y'

-- Update client statistics for improved query plans
--exec sp_updatestats
Posted in Database, Programming, SQL | Tagged | Leave a comment

How to count the number of connections to a SQL database

Recently we had an issue where, despite all our code using SQL connections in using statements, we inexplicably leaked database connections. It turned out to be caused by a memory leak in .Net 4.6.2 and a simple framework upgrade fixed the leak, but until then, we ran a script to count the number of connections and then closed and restarted the affected application as necessary.

The script I ran to count the number of connections was as follows… (Simply change  the database name in the WHERE clause. Or remove the WHERE clause completely if you want to see all your connections.)

If you sensibly use application-specific logins, it should be easy to track down any rogue applications that use too many connections.

SELECT pc.hostname,
       pc.program_name,
       pc.cmd,
       pc.loginame,
       sdb.name,
       count(pc.dbid) NumberOfConnections
FROM sys.dm_exec_connections ec
    INNER JOIN sys.sysprocesses pc ON pc.spid = ec.session_id
    INNER JOIN sys.databases sdb ON pc.dbid = sdb.database_id
where sdb.name = 'YourDatabaseName'     
group by pc.hostname,
         pc.program_name,
         pc.cmd,
         pc.loginame,
         sdb.name

The output looks something like this:

image

Posted in Database, SQL | Tagged | Leave a comment

Beware: State pattern + SELECT TOP N + unhandled exceptions = bottleneck

Hopefully the title of this makes it clear enough… because I can’t go into too much detail. I can’t show you the code because it isn’t mine to show, but there are so many layers it would take several posts to show you anyway. I just want to get the weakness in a state driven design pattern across, at least if you don’t implement it carefully enough.

I’m on leave, you see. But I received a call around 4:30PM because of a failing verification service used in a website that’s part of our legacy software system – and is used by a major company in all their stores nationwide. It’s a (semi) real time system that store workers use to verify potential clients’ details before giving them contracts. If the system stops working, all those stores are affected nationwide, and cashflow stops. Business don’t like it much when cashflow stops.

The way it works from a user perspective is:

  1. Worker enters client name, banking details, and so on.
  2. System verifies client details.
  3. Worker sees the results on a grid, and if valid, worker continues to open contract.

That seems simple enough, but the architecture is not real time, as one might expect. What really happens is:

  1. Users all over the country are entering details. When they capture the details, the website simply inserts a request with pending status.
  2. A service application fires off a thread every N seconds, collects a configurable amount of the oldest pending requests (let’s pretend it just verifies one thing from here on for simplicity) that have the pending status, and dispatches them off to whatever class or other code that does the work of verifying.
  3. Once all the results are in, the grid on the client side is updated with the relevant data.

Simple enough, right? That’s pretty much the way the architect designed it. But he also used another common pattern…

  • All operations are always done in a single transaction.
  • If anything goes wrong, roll back the transaction, log the error, and send out a notification.

That worked wonderfully for a few years, while the system was small… before it was in thousands of stores nationwide. The one and only support person back then quickly saw when things weren’t working, and fixed any issues fairly quickly.

The thing is, when there’s an unhandled exception by the backend service, can you guess what happens?

Assuming the schedule runs every 3 seconds and picks up the oldest 10 requests. Something like this…

SELECT TOP 10
    *
FROM Request
WHERE Status = 'Pending'
ORDER BY ID ASC

What happens is:

  1. In practice, we get the 10 requests.
  2. One of them fails for reasons unknown (and outside of the scope here). There’s an unhandled exception. An obscure message is emailed to support but nobody saw it for some reason. (Maybe because there are dozens of services that do this and no one can keep up with those emails.) Eventually, if no one stops these emails every 3 seconds, the hosting provider will block all those emails anyway, because in practice, there are thousands of these errors.
  3. Nobody notices a problem because 9 requests are processed successfully and 3 seconds later, the service will poll again.
  4. Next time the service polls, that same request will fail the same way; nobody reads the error email, but it’s OK because 9 requests were processed successfully, and every 3 seconds, 9 requests are attended to.
  5. Until another request fails for the same reason. Now we have the oldest 2 that will always fail, and only 8 requests can be processed.
  6. You can see where this is going now, right? Every so often, another request will fail, and another “slot” will be taken up by a request that will always fail. This could take days, weeks, or even months in practice, but eventually, all slots of the queue are filled with requests that will always fail and be tried again.
  7. Eventually, all 10 requests will fail, every one of them rolls back, and the next time we poll, we will try, and fail, to process those same 10 requests.
  8. At this point, the entire system comes to a grinding halt. Because we SELECT TOP 10, and those requests will always fail, the system will never get to anything else in the queue until someone manually intervenes.

What we have now is a system wide infinite loop as it will try and fail to process the same 10 requests over and over again, forever. Throughout the country, users are refreshing their screens to see their results, but those requests are just sitting there, not being processed at all. Long lines of unhappy customers are building up in all these stores nationwide and we have a fully fledged disaster.

(I lied in point 2. That’s what normally happens, but today there was no error. The log simply showed it finding 10 requests to process every few seconds, and nothing else. That’s what they knew when they called me. That’s as far as they got, and they were stuck. To be fair, this was a project manager and junior developer, one who has no experience with this system.)

The moral to this story is simple: Do not leave exceptions unhandled.

I’ve made it sound really simple, but this is a bug that spans across different applications in the same system. There’s an admin portal with complex client configuration, a portal used in the stores, a backend service that everybody forgets even exists, and there’s staff turnover. What happened was, nobody knew about this design flaw except me… they knew about a problem on one client that could cause certain requests to fail, but they did not understand the consequence of it. It’s a bug I’d love to fix, but this isn’t even the system I work on… I work mostly on another legacy system which is even more complicated than this one.

But such is life, I guess… Anyway, if those exceptions were properly handled and the state was simply changed to an error state, the rest of the state driven design would have handled it correctly. But the bottom line is this: Always handle exceptions appropriately. Don’t assume everything will work as expected.

Posted in Programming | Tagged | Leave a comment

Checking that a Uri string is valid with c#

OK, you should never need to do this – that’s what my gut tells me. But for some annoying reason, we have code failing in production on sending web hooks… and that’s after I used Uri.TryCreate. This code combines Uri.IsWellFormedUriString with TryCreate. It should be unnecessary, looking at the Microsoft code. But fuck it, I am tired of this bug.

public static bool IsValidUri(this string value, UriKind uriKind = UriKind.Absolute)
{
    Uri uri;
    if (!string.IsNullOrEmpty(value) && Uri.TryCreate(value, uriKind, out uri) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
    {
        return Uri.IsWellFormedUriString(value, uriKind);
    }

    return false;
}

But still – don’t do that. You should just call Uri.IsWellFormedUriString on its own.

Posted in Programming | Tagged , | Leave a comment

Don’t write shitty extension methods

OK, I guess this is part 3 of this reluctantly written series. Here’s some truly shit code:
(It’s a screenshot because this is not code you should use. Also I know this overlaps the blog menu on the right, but I think that suits the fuckery of this code. If you really want to read it, click it and view in a new tab.)

No

The first thing I hate about all this dude’s code is that he wrote extension methods that call each other. There are no references to this ChangeType method, but there are plenty of references to ReturnAs, so this abomination is an example of bad code I have not been able to remove from our current code base.

Removing the references to this weird and unnecessary cast is not as easy as it should be. A pattern that repeats throughout his code (and I am very close to publishing his name; the dude’s code is so terrible), is some weird and not so wonderful handling of null. Some of his methods claim to be “safe” but they return null, or worse yet, return unexpected default values when they encounter null. Cleaning this up becomes a matter of analysing all the references and…

Actually fuck it. Look at this shit. All of these could have been explicit casts. (These you need to open in a new tab to read.)

WhyInTheSweetFuck

There is no reason. Not one fucking reason for any of this code to exist.

Edit… It seems most of the calls to the weird casting method above come from one of the other extension methods – specifically the GetValue method that can return a default(T). I don’t have the energy to do this shit.

ForFuckSake

Posted in Programming | Tagged , , | Leave a comment

Seriously, if you write code like this, I will fucking smack you

OK, so I’ve mostly given up on cleaning up all this code. There are so many references to unnecessary extension methods, I’ll never be able to remove them all.

Anyway, I see this file happens to have some of my own extension methods, as well as some of the weird ones. Please note, the ToArray extension method here is mine – I wrote it years ago when I was high on meth. By the way, I’ve been clean since September 2013, but still. My point is that this other code is so bad, even my meth-head days code is better than it.

Screenshot 2021-10-28 112919

Posted in Programming | Tagged , , | Leave a comment

A threadsafe replacement for List<T>

Wow! An opportunity to write an actual programming post for once. Apologies… readers, this doesn’t happen much these days – so I hope you like this one.

I had a bit of an odd requirement recently… I need to populate a List<T> of requests from multiple threads… I mean Tasks. To be honest, I’d normally tell my fellow developers not to do this. Concurrent collections exist. And they work. If you need to populate a collection from multiple threads, you should probably use a ConcurrentBag<T> and be done with it…

However, I’m working with a complex framework of existing legacy code, one which previously populated a List<T> of polymorphic requests synchronously via various methods, and then passed that collection downstream to various other code. In the process of refactoring, I have completely rewritten the transaction “broker” to be asynchronous. So as not to reinvent wheels, some of my new code, specifically around adding to the collection via SQL in the various methods run in parallel, is very similar to the old code. I’m just taking advantage of the built in async methods working with data readers and such. I’ve also made everything everywhere async, because the TAP pattern tends to spread like that. But it would be madness to change the signatures of all the other code expecting a List<T>. Actually I did change it, but a small change, to IList<T>, which was manageable and didn’t break too much. (In general, it is safer to pass interfaces around anyway, such as IList<T> or IEnumerable<T>. But again – I did not write this framework; I’m fixing bugs and making it more scalable, and so on.)

Please note, the purpose of this class is not to enumerate a collection while it gets modified from other threads. That would be stupid. Don’t do that. Don’t ever do that. This is meant to be populated by parallel threads, such that we can wait for all those threads to finish, and then pass the collection downstream to other code. (To me, threadsafe means safe for writing from multiple threads. If ever I call anything threadsafe, that’s what I mean. You should never consider any collection safe for reading from multiple threads. If you want to read something that could be modified while you read it, you need to read a copy. But that’s not what this post is about.)

Also, I found a variation of this class on StackOverflow somewhere, and then modified it for my needs.  It’s actually a copy of an internal Microsoft framework class. (As far as I recall, I only changed the constructor, since I want to be able to instantiate it with or without an existing collection.) In the previous paragraph, I added the note about threadsafe being for simultaneous writing, not reading, because I saw some of the commenters on that StackOverflow page didn’t seem to understand.

So, for an example so that this makes sense, this is how I use the class… This is an example where I have changed the names of the real transaction types into dummy example types. In this example, I have 3 tasks that populate the collection in parallel. The code sets up the tasks and then waits for them all to complete. (Details not shown or relevant here.) We then pass the collection on to other code that expects an IList<T>.

public async Task Export(
    TransactionCategory category = TransactionCategory.Contracts | TransactionCategory.Debits |
                                   TransactionCategory.Credits, DateTime? actionDate = null,
    List<int> clients = null)
{
    SynchronizedList<Request> outgoingTransactions = new SynchronizedList<Request>();

    try
    {
        var tasks = new List<Task>
        {
            Task.Run(async () =>
            {
                // Contracts
                if (category.HasFlag(TransactionCategory.Contracts))
                    await GenerateContracts(
                        actionDate.HasValue
                            ? actionDate.Value
                            : DateTime.Today,
                        clients);
            }),
            Task.Run(async () =>
            {
                // Debits
                if (category.HasFlag(TransactionCategory.Debits))
                    await GenerateDebits(
                        actionDate.HasValue
                            ? actionDate.Value
                            : DateTime.Today,
                        clients);
            }),
            Task.Run(async () =>
            {
                // Credits
                if (category.HasFlag(TransactionCategory.Credits))
                    await GenerateCredits(
                        actionDate.HasValue
                            ? actionDate.Value
                            : DateTime.Today,
                        clients);
            })
        };

        await Task.WhenAll(tasks);

        if (outgoingTransactions.Count > 0)
        {
            await othercode.SendTransactions(outgoingTransactions);
        }
    }
    catch (AggregateException ax)
    {
        ax.Handle(e =>
        {
            ErrorMessage("Error: An error occurred while brokering transactions: " + e.Message);
            return true;
        });
    }
}

And the actual synchronized list code is simply this: (As you an see, it simply locks access to the inner collection.)

using System;
using System.Collections.Generic;

namespace Example
{
    /// <summary>
    /// Allows us passing around an IList&lt;T&gt; that can be populated safely from parallel threads.
    /// </summary>
    [Serializable()]
    public sealed class SynchronizedList<T> : IList<T>
    {
        #region Private Fields

        private List<T> _list;
        private Object _root;

        #endregion Private Fields

        #region Public Constructors

        public SynchronizedList(List<T> list)
        {
            _list = list;
            _root = ((System.Collections.ICollection)list).SyncRoot;
        }

        public SynchronizedList()
        {
            _list = new List<T>();
            _root = ((System.Collections.ICollection)_list).SyncRoot;
        }

        #endregion Public Constructors

        #region Public Properties

        public int Count
        {
            get
            {
                lock (_root)
                {
                    return _list.Count;
                }
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return ((ICollection<T>)_list).IsReadOnly;
            }
        }

        #endregion Public Properties

        #region Public Indexers

        public T this[int index]
        {
            get
            {
                lock (_root)
                {
                    return _list[index];
                }
            }
            set
            {
                lock (_root)
                {
                    _list[index] = value;
                }
            }
        }

        #endregion Public Indexers

        #region Public Methods

        public void Add(T item)
        {
            lock (_root)
            {
                _list.Add(item);
            }
        }

        public void AddRange(IEnumerable<T> items)
        {
            lock (_root)
            {
                foreach (var item in items)
                {
                    _list.Add(item);
                }
            }
        }

        public void Clear()
        {
            lock (_root)
            {
                _list.Clear();
            }
        }

        public bool Contains(T item)
        {
            lock (_root)
            {
                return _list.Contains(item);
            }
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            lock (_root)
            {
                _list.CopyTo(array, arrayIndex);
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            lock (_root)
            {
                return _list.GetEnumerator();
            }
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            lock (_root)
            {
                return ((IEnumerable<T>)_list).GetEnumerator();
            }
        }

        public int IndexOf(T item)
        {
            lock (_root)
            {
                return _list.IndexOf(item);
            }
        }

        public void Insert(int index, T item)
        {
            lock (_root)
            {
                _list.Insert(index, item);
            }
        }

        public bool Remove(T item)
        {
            lock (_root)
            {
                return _list.Remove(item);
            }
        }

        public void RemoveAt(int index)
        {
            lock (_root)
            {
                _list.RemoveAt(index);
            }
        }

        #endregion Public Methods
    }
}
Posted in Programming | Tagged , , | Leave a comment

Please don’t write unnecessary extension methods

Apologies that I don’t get much time to write here anymore. I’m just writing this to mention… if you should ever work with me, and you write extension methods like this, I will smack you. I mean, I won‘t, but I’ll wish that I could.

Screenshot 2021-10-11

Recently I’ve been removing a lot of extension methods that were added by a developer who has since been fired, but whose ghost haunts the code. This is nowhere near the worst of his code, which included lovely names like IsHasItems. In fact the worst of his code was methods that looked similar to well known Linq extensions… GetFirstOrDefault, which were then called by some developers by mistake. Those were the worst ones to remove.

I haven’t shown you all the code even related to these few methods, just a random block because of one or two method I’m deleting right now, but trust me… you don’t need to see more. The IsValiddateTime implementation doesn’t do anything useful either. I’m almost impressed with the hierarchy of useless extension methods that call each other, that this individual has managed to achieve. Almost.

Also, I know you have to validate your inputs, but if the compiler hint tells you something can never be null, maybe don’t check for null, knucklehead.


OK, so a Facebook notification brought my attention back to this shit, so I’ll add something… I didn’t even mention how much I hate that first method. It’s a method whose name implies that it switches around two dates. But it doesn’t do what it claims to, not always anyway. Why? Why would you ever do this? And as stated, this isn’t even the worst. These method names are already greyed out because they were only called by other extension methods that I’ve already deleted. You don’t want to know what those other abominations did.

Posted in Design, Programming | Tagged , | Leave a comment

Given a List that contains multiple objects with the same property, split it into smaller lists grouped by the distinct property

Oh, that title doesn’t make this very clear at all, does it? Fuck.

So we had a problem at work today. We have a number of transaction statuses to insert into a system, and do some other work on them. The system broke because they contained “duplicates”, which weren’t true duplicates but rather statuses against the same ID, which happens to be the primary key of a temporary table that’s used in a bulk insert. The code doesn’t cope very well with multiple statuses against the same ID for the same date, and there were thousands of them like this. The data isn’t such that we can use a different key, so my colleague fixed it in the production system with a SQL query and a temp table, manipulating the data such that the process could be run multiple times in stages.

I then came up with a code fix, which I first implemented in a little console application to test the principle. It’s that console application I can share.

In the example below, I have a simplified class to demonstrate the problem, called a Payment. A payment has three properties, an ID, a Status, and a Date. What the code needs to do is, given a list that contains multiple entries with the same ID, break the list into smaller lists of distinct IDs. To achieve this, I use an extension method and a yield return statement.

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication2
{
    class Payment
    {
        public int ID { get; set; }
        public string Status { get; set; }
        public DateTime Date { get; set; }
    }

    static class Extensions
    {
        public static IEnumerable<List<Payment>> BatchPaymentById(this IEnumerable<Payment> payments)
        {
            var list = payments.ToList();
            var result = list.GroupBy(p => p.ID, (key, g) => g.OrderBy(e => e.ID).First()).ToList();

            if (result.Count != list.Count)
            {
                while (list.Count > 0)
                {
                    list.RemoveAll(p => result.Contains(p));
                    yield return result;
                    result = list.GroupBy(p => p.ID, (key, g) => g.OrderBy(e => e.ID).First()).ToList();
                }
            }
            else yield return list;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var today = DateTime.Today.Date;
            var payments = new List<Payment>();
            payments.Add(new Payment { ID = 1, Status = "Success", Date = today });
            payments.Add(new Payment { ID = 1, Status = "Dispute", Date = today });
            payments.Add(new Payment { ID = 2, Status = "Success", Date = today });
            payments.Add(new Payment { ID = 2, Status = "Dispute", Date = today });
            payments.Add(new Payment { ID = 3, Status = "Success", Date = today });

            foreach (List<Payment> paymentList in payments.BatchPaymentById())
            {
                // Do something with the batch
            }
        }
    }
}
Posted in Programming | Tagged , , | Leave a comment

Here’s the first version of my code using Microsoft.IO.RecyclableMemoryStream

I mentioned a while back that I was going to do this…

The app is heavy with its usage of streams, so replacing all references to MemoryStream with one that reuses pooled buffers seemed like a logical thing to do. I already use an object pool for thumbnail control instances (the most used object in the application), so it seems like an obvious way to improve performance and possibly memory management too. And it works well.

If you are interested in reading about RecyclableMemoryStream, read their announcement post.

If you missed my post describing what my code does, you can read it here. The code is still shared in the same place on Google drive here.

As for the code, usage is quite straightforward… You create an instance of their memory stream manager, and instead of creating MemoryStream instances as you normally would, use its GetStream method. That’s about it… The only thing that changed for me is when using such streams as read-only buffers (the MemoryStream(Byte[]) constructor) because this package doesn’t have a GetStream() method with identical signature. They do have an equivalent one though… it just takes a couple of extra parameters, such as tag, offset, and length. My implementation of the code changes in my solution simply used a Singleton for the memory stream manager, and then I had to replace all occurrences of “new MemoryStream…) in the code, which was slightly tedious because of the different signature for read-only mode streams.

Posted in Programming | Tagged | Leave a comment

Another example of a control hosted in a ToolStrip: ToolStripCustomProgressBar

First off, while taking screenshots to use in this post, I found an issue in my ffmpeg wrapper, which specified “libvo_aacenc” for the aac audio encoder. It turns out that has since changed and because I recently updated the ffmpeg referenced, I had to change it to use “aac” for the built-in encoder. The source code archive has been updated. By the way, I use the Windows 64-bit and 32-bit static builds of ffmpeg, from this site.

I originally shared the code for my custom ProgressBar in September 2013.

That’s a progress bar that does some custom drawing to show a disabled state, and in the dialogs shown below, my code to suspend a process is used when the user clicks the Pause/Resume button to suspend or resume the underlying ffmpeg process that’s being controlled. (Actually I wrote the pause/resume process code and this control entirely because of this foolish feature that allows you to do as many simultaneous video conversions as you like. I also allow you to set the video conversion process priority higher than the application process priority. Good luck clicking those buttons then. If I were smarter, I’d probably have limited the number of allowed simultaneous conversions and had one batch dialog to show them all. But I’m not that smart. Sorry.)

So… this is what the custom progress bar normally looks like:

SNAGHTML4eb7f9d2

And here it is disabled when the video conversion is paused:

SNAGHTML4eb8c10a

(Once again, these dialogs are using my stupid code that draws shadows for common controls. I can’t remember if I shared that but can’t seem to find it on this blog.)

You can refer to the old post or the full source code archive (see the first paragraph of this post) for the progress bar itself. I just wanted to show (again) how easy it is to host a control in a ToolStrip or StatusStrip.

Here’s the hosted control, showing normal and disabled state in a StatusStrip… To force a long-running process, I used a feature of my application that allows the user to precache all thumbnails in a hierarchy of nested directories. One of my less intuitive/intelligent pieces of code that supports being paused by clicking on the label next to the progress bar.

SNAGHTML4ebfb2ef

SNAGHTML4ec0234c

And here’s the Toolstrip-hosted code for that control… I’ve shared this simply to show again how easy it is to host such a control… the actual code required to get all this working isn’t complete in this post, because I didn’t include the interface where the disabled progress bar colours are defined.

The point here is to emphasize how easy it is to host such a control. In this case, the control hosted derives from ProgressBar, so all the relevant properties and methods of the hosted (wrapped) control are exposed such that using it programmatically is then exactly the same as using a ProgressBar directly.

using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace Romy.Controls
{
    /// <summary>A ProgressBar that uses custom drawing to show if it is disabled, hosted in a ToolStrip.</summary>
    [ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.ToolStrip | ToolStripItemDesignerAvailability.StatusStrip)]
    public class ToolStripCustomProgressBar : ToolStripControlHost, IDisabledProgressBarColors
    {
        public ToolStripCustomProgressBar()
            : base(CreateControlInstance())
        { }

        [Description("The color used for the bottom of the ProgressBar background when it is disabled."), Category("Disabled Appearance")]
        public Color BackGradientBottomColor
        {
            get => ProgressBar.BackGradientBottomColor;
            set => ProgressBar.BackGradientBottomColor = value;
        }

        [Description("The color used for the middle of the ProgressBar background when it is disabled."), Category("Disabled Appearance")]
        public Color BackGradientMiddleColor
        {
            get => ProgressBar.BackGradientMiddleColor;
            set => ProgressBar.BackGradientMiddleColor = value;
        }

        [Description("The color used for the top of the ProgressBar background when it is disabled."), Category("Disabled Appearance")]
        public Color BackGradientTopColor
        {
            get => ProgressBar.BackGradientTopColor;
            set => ProgressBar.BackGradientTopColor = value;
        }

        [Description("The color used for the bottom border of the ProgressBar when it is disabled."), Category("Disabled Appearance")]
        public Color BorderGradientBottomColor
        {
            get => ProgressBar.BorderGradientBottomColor;
            set => ProgressBar.BorderGradientBottomColor = value;
        }

        [Description("The color used for the top border of the ProgressBar when it is disabled."), Category("Disabled Appearance")]
        public Color BorderGradientTopColor
        {
            get => ProgressBar.BorderGradientTopColor;
            set => ProgressBar.BorderGradientTopColor = value;
        }

        [Description("The color used for the bottom of the ProgressBar progress value when it is disabled."), Category("Disabled Appearance")]
        public Color ProgressGradientBottomColor
        {
            get => ProgressBar.ProgressGradientBottomColor;
            set => ProgressBar.ProgressGradientBottomColor = value;
        }

        [Description("The color used for the middle of the ProgressBar progress value when it is disabled."), Category("Disabled Appearance")]
        public Color ProgressGradientMiddleColor
        {
            get => ProgressBar.ProgressGradientMiddleColor;
            set => ProgressBar.ProgressGradientMiddleColor = value;
        }

        [Description("The color used for the top of the ProgressBar progress value when it is disabled."), Category("Disabled Appearance")]
        public Color ProgressGradientTopColor
        {
            get => ProgressBar.ProgressGradientTopColor;
            set => ProgressBar.ProgressGradientTopColor = value;
        }

        [Description("The color used to highlight the bottom of the ProgressBar progress value when it is disabled."), Category("Disabled Appearance")]
        public Color ProgressHighlightColor
        {
            get => ProgressBar.ProgressHighlightColor;
            set => ProgressBar.ProgressHighlightColor = value;
        }

        public CustomProgressBar ProgressBar => Control as CustomProgressBar;

        public int Maximum
        {
            get => ProgressBar.Maximum;
            set => ProgressBar.Maximum = value;
        }

        public int Minimum
        {
            get => ProgressBar.Minimum;
            set => ProgressBar.Minimum = value;
        }

        public int Value
        {
            get => ProgressBar.Value;
            set => ProgressBar.Value = value;
        }

        public ProgressBarStyle Style
        {
            get => ProgressBar.Style;
            set => ProgressBar.Style = value;
        }

        public void Increment(int value) => ProgressBar.Increment(value);

        public void PerformStep() => ProgressBar.PerformStep();

        private static Control CreateControlInstance() => new CustomProgressBar { Size = new Size(100, 16) };
    }
}
Posted in Programming | Tagged , | Leave a comment

I see my ImageComboBox and ToolStripImageComboBox was broken.

So I fixed it. Not sure when it broke. Also I don’t think I shared it before… Anyway, here it is now, and it’s fixed in my full source code archive too. The issue was the native struct pinvoke declaration for ComboboxInfo was wrong. Bizarrely, that caused the ComboBox to display its image correctly in the designer but it failed to get the control’s handle at runtime, and I never noticed because I don’t really use it for anything.

Similarly to my recently shared Slider/ToolStripSlider control, I have a custom ComboBox control, called an ImageComboBox that displays an image, kind of like the search box in Windows explorer, and a corresponding control that I use on a ToolStrip. It doesn’t display images for items in the ComboBox because that’s really fucking annoying and unnecessary, but does display an image for the top level of the control. Typically I give it a 16×16 glyph of some sort. Here’s the working version…

It looks like this – the search ComboBox displayed on the top right of my main form:

SNAGHTML49086cf9

First the native methods and structs… I’ve only copied the one method in the NativeMethods class used by this control so hopefully I didn’t forget anything.

We need these because we use a native window to get to the edit window inside the ComboBox, then draw an image on there. Actually that’s where this code went wrong… My ComboBoxInfo declaration was missing the button states. Maybe that was changed since I last used this.

using System.Runtime.InteropServices;
using System.Security;

namespace Romy.Controls
{
    [SuppressUnmanagedCodeSecurity]
    internal static class NativeMethods
    {
        [DllImport("user32.dll")]
        public static extern bool GetComboBoxInfo(System.IntPtr hwndCombo, ref ComboBoxInfo info);
    }
}
using System;
using System.Runtime.InteropServices;

namespace Romy.Controls
{
    [StructLayout(LayoutKind.Sequential)]
    internal struct RECT
    {
        public int left;

        public int top;

        public int right;

        public int bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct ComboBoxInfo
    {
        public Int32 cbSize;

        public RECT rcItem;

        public RECT rcButton;

        public ComboBoxButtonState buttonState;

        public IntPtr hwndCombo;

        public IntPtr hwndEdit;

        public IntPtr hwndList;
    }

    internal enum ComboBoxButtonState
    {
        STATE_SYSTEM_NONE = 0,
        STATE_SYSTEM_INVISIBLE = 0x00008000,
        STATE_SYSTEM_PRESSED = 0x00000008
    }
}

Then the control and its NativeWindow class… Not much code here really. We extend the control and intercept some Windows messages sent to its edit window and change the way it gets painted, shoving our image in there on the right. I also added a call to SelectAll when the control is clicked, because typically I use it for a search ComboBox, and when I click it, I want to overwrite anything already typed there. Feel free to remove that if you don’t like it. And lastly I added some code to allow pasting into the control. It’s been a while, but as far as I can remember, pasting into the control didn’t work out of the box.

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace Romy.Controls
{
    /// <summary>A ComboBox that supports an image in its TextBox part. The image is
    /// drawn on the right of the edit box.</summary>
    /// <remarks>Originally this was based on a component with the same name I found
    /// on CodeProject. However, since I was only interested in an image in the edit
    /// box part (to create a standard-looking search ComboBox), I stripped out all
    /// other functionality.</remarks>
    public class ImageComboBox : ComboBox
    {
        private readonly ComboEditWindow editBox;

        public ImageComboBox()
        {
            editBox = new ComboEditWindow(this);
            DoubleBuffered = true;
        }

        [Category("Appearance"), Description("The Image to display for the editBox part of the ComboBox."),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public Image Image
        {
            get => editBox.Image;
            set => editBox.Image = value;
        }

        /// <summary>Suport Ctrl+V pasting text into the ComboBox.</summary>
        public override bool PreProcessMessage(ref Message msg)
        {
            Keys keys = (Keys)msg.WParam.ToInt32();

            if (keys == Keys.V && ModifierKeys == Keys.Control)
            {
                Text = Clipboard.GetDataObject().GetData(typeof(string)) as string;
                return string.IsNullOrEmpty(Text);
            }

            return base.PreProcessMessage(ref msg);
        }

        protected override void OnClick(EventArgs e)
        {
            SelectAll();
            base.OnClick(e);
        }
    }
}
using System.Drawing;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Windows.Forms;

namespace Romy.Controls
{
    /// <summary>A helper class that accesses the windows message stream directed towards the Edit
    /// portion of the ComboBox. Gets assigned the handle of the TextBox of the ComboBox.</summary>
    public sealed class ComboEditWindow : NativeWindow
    {
        private ComboBoxInfo cbxinfo;

        private ImageComboBox Owner;

        private const int WM_CHAR = 0x102;

        private const int WM_GETTEXT = 0xd;

        private const int WM_GETTEXTLENGTH = 0xe;

        private const int WM_KEYDOWN = 0x100;

        private const int WM_KEYUP = 0x101;

        private const int WM_LBUTTONDOWN = 0x201;

        private const int WM_PAINT = 0xF;

        private const int WM_SETCURSOR = 0x20;

        private readonly Control parent;

        public ComboEditWindow(Control parent)
        {
            cbxinfo = new ComboBoxInfo();

            this.parent = parent;
            parent.HandleCreated += (sender, e) => AssignTextBoxHandle(sender as ImageComboBox);
            parent.HandleDestroyed += (sender, e) => ReleaseHandle();
        }

        public Image Image { get; set; }

        /// <summary>The native window's original handle is released
        /// and the handle of the TextBox is assigned to it.</summary>
        public void AssignTextBoxHandle(ImageComboBox owner)
        {
            Owner = owner;
            cbxinfo.cbSize = Marshal.SizeOf(cbxinfo);
            NativeMethods.GetComboBoxInfo(Owner.Handle, ref cbxinfo);
            AssignHandle(cbxinfo.hwndEdit);
        }

        /// <summary>Whenever the textbox is repainted, draw the image.</summary>
        public void DrawImage()
        {
            if (Image != null)
            {
                new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();

                using (Graphics graphics = Graphics.FromHwnd(Handle))
                {
                    if (Owner != null)
                    {
                        using (SolidBrush solidBrush = new SolidBrush(Owner.BackColor))
                        {
                            graphics.FillRectangle(solidBrush, new Rectangle((int)(graphics.VisibleClipBounds.Width - Image.Width), 0, Image.Width, (int)graphics.VisibleClipBounds.Height));
                        }
                    }
                    graphics.DrawImage(Image, graphics.VisibleClipBounds.Width - Image.Width, 0);
                }
            }
        }

        /// <summary>Override the WndProc method to redraw the
        /// TextBox when the textbox is repainted.</summary>
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WM_PAINT:
                case WM_LBUTTONDOWN:
                case WM_KEYDOWN:
                case WM_KEYUP:
                case WM_CHAR:
                case WM_GETTEXTLENGTH:
                case WM_GETTEXT:
                    base.WndProc(ref m);
                    DrawImage();
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }
    }
}

Originally I started the above control with one I’d downloaded from CodeProject. (This was years ago and I didn’t save the URL.) The control I used did a lot more and painted images for the items too… We tend to forget this but a ComboBox is actually a composite control – a text box with a drop down list view. So it has separate Windows handles for its different parts… One for the edit window, one for the list view, maybe a button on the right, etc. I don’t really know the details. I stripped out the functionality I didn’t want, but also ended up rewriting the control almost completely, taking the design for the NativeWindow in terms of when to assign its handle, almost as is from the MSDN sample. As for which Windows messages to intercept, I changed that too. All I did was debug and look at the messages received, to choose which ones need the image to be painted.

And to host it on a ToolStrip, it uses this ToolStripControlHost-derived class…

This is very similar to my last example that hosted my custom Slider. The pattern to host a control on a ToolStrip is quite straightforward. Derive a class from ToolStripControlHost; create the control in a method called by your constructor – mine is always called CreateControlInstance; pick the properties in the hosted control you need to expose; and lastly expose any events of the hosted control (as many as you like) and hook/unhook them in your overridden OnSubscribeControlEvents and OnUnsubscribeControlEvents. Once you’ve done it once or twice, it only takes a couple of minutes to write the code to host whatever control you want.

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace Romy.Controls
{
    /// <summary>ToolStripControlHost-derived control to support hosting a ComboBox with an
    /// image in its editBox part on the ToolStrip family of container controls.</summary>
    [ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.None | ToolStripItemDesignerAvailability.ToolStrip | ToolStripItemDesignerAvailability.StatusStrip)]
    public class ToolStripImageComboBox : ToolStripControlHost
    {
        public ToolStripImageComboBox()
            : base(CreateControlInstance())
        { }

        public ComboBox.ObjectCollection Items => IsDisposed ? null : ImageComboBoxControl.Items;

        public ComboBoxStyle DropDownStyle
        {
            get => ImageComboBoxControl.DropDownStyle;
            set => ImageComboBoxControl.DropDownStyle = value;
        }

        public DrawMode DrawMode
        {
            get => ImageComboBoxControl.DrawMode;
            set => ImageComboBoxControl.DrawMode = value;
        }

        [Description("This property is not applicable for this type.")]
        public override Image BackgroundImage
        {
            get => null;
            set => base.BackgroundImage = null;
        }

        [Category("Appearance")]
        [Browsable(true)]
        [Description("The Image to display for the EditBox part of the ComboBox.")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public new Image Image
        {
            get => ImageComboBoxControl.Image;
            set => ImageComboBoxControl.Image = value;
        }

        public ImageComboBox ImageComboBoxControl => Control as ImageComboBox;

        [Description("This property is not applicable for this type.")]
        public override ImageLayout BackgroundImageLayout
        {
            get => base.BackgroundImageLayout;
            set { }
        }

        public new int Height
        {
            get => ImageComboBoxControl.Height;
            set => ImageComboBoxControl.Height = value;
        }

        public int MaxDropDownItems
        {
            get => ImageComboBoxControl.MaxDropDownItems;
            set => ImageComboBoxControl.MaxDropDownItems = value;
        }

        public int MaxLength
        {
            get => ImageComboBoxControl.MaxLength;
            set => ImageComboBoxControl.MaxLength = value;
        }

        public int SelectedIndex
        {
            get => ImageComboBoxControl.SelectedIndex;
            set => ImageComboBoxControl.SelectedIndex = value;
        }

        public new int Width
        {
            get => ImageComboBoxControl.Width;
            set => ImageComboBoxControl.Width = value;
        }

        public object SelectedItem
        {
            get => ImageComboBoxControl.SelectedItem;
            set => ImageComboBoxControl.SelectedItem = value;
        }

        protected override Size DefaultSize => new Size(100, 22);

        public override string Text
        {
            get => ImageComboBoxControl.IsHandleCreated ? base.Text : string.Empty;
            set => base.Text = value;
        }

        public new event KeyPressEventHandler KeyPress;

        public event EventHandler SelectedIndexChanged;

        public new event EventHandler TextChanged;

        public void BeginUpdate() => ImageComboBoxControl.BeginUpdate();

        public void EndUpdate() => ImageComboBoxControl.EndUpdate();

        protected override void OnSubscribeControlEvents(Control control)
        {
            base.OnSubscribeControlEvents(control);

            ImageComboBoxControl.SelectedIndexChanged += new EventHandler(ImageComboBoxControl_SelectedIndexChanged);
            ImageComboBoxControl.KeyPress += new KeyPressEventHandler(ImageComboBoxControl_KeyPress);
            ImageComboBoxControl.TextChanged += new EventHandler(ImageComboBoxControl_TextChanged);
        }

        protected override void OnUnsubscribeControlEvents(Control control)
        {
            base.OnUnsubscribeControlEvents(control);

            ImageComboBoxControl.SelectedIndexChanged -= new EventHandler(ImageComboBoxControl_SelectedIndexChanged);
            ImageComboBoxControl.KeyPress -= new KeyPressEventHandler(ImageComboBoxControl_KeyPress);
            ImageComboBoxControl.TextChanged -= new EventHandler(ImageComboBoxControl_TextChanged);
        }

        private static Control CreateControlInstance() => new ImageComboBox { Size = new Size(100, 22) };

        private void ImageComboBoxControl_KeyPress(object sender, KeyPressEventArgs e) => KeyPress?.Invoke(this, e);

        private void ImageComboBoxControl_SelectedIndexChanged(object sender, EventArgs e) => SelectedIndexChanged?.Invoke(this, e);

        private void ImageComboBoxControl_TextChanged(object sender, EventArgs e) => TextChanged?.Invoke(this, e);
    }
}

One cool thing about creating your own hosted ToolStrip controls is that they show up in the editor just like standard ones. For example, in my own solution the controls are all in a class library project that’s part of the solution, referenced by the application project, so when clicking the button to add more controls on my form in the designer, I can pick standard controls or my own ones, and it looks like this:

image

Posted in Programming | Tagged , , , | Leave a comment

My custom ToolStripSlider Windows Forms control

In the last post I shared the full source code of my jack-of-all-trades application.

Since you can get the code there, I can now share classes and the code for controls, even if they refer to image resources in the project, because you can get it all from the zip file of the full source code.

When I created those controls, I was particularly happy with the ToolStripSlider control. It’s basically a Trackbar-alternative control in a ToolStrip, except instead of using a standard trackbar, it inherits from ToolStripControlHost, and hosts my Slider custom control, because that’s how you render other controls in a ToolStrip.

I originally wrote the Slider control because I just don’t like the way a standard Trackbar behaves. It has a couple of baffling SmallChange and LargeChange properties, and really doesn’t seem intuitive at all to use. All I wanted was a slider to use for seeking videos and setting the volume. It’s behaviour is totally the way I wanted it… That is, when you click it anywhere, it sets the value to one that corresponds with the click, and moves the tracker to the location where you clicked it. Also when you click it, it immediately sets its state to pressed, and allows you to drag the slider to a new location, then sets the state back to normal when you release the mouse or it loses focus.

In the app’s video player form shown below, I used the Slider control twice… The controls at the bottom are on a panel, with a Slider control docked to the top to act as a seeker control, and a ToolStripSlider control shown on the right of the ToolStrip, which is docked to the bottom of the panel.

It looks like this when using my normal theme…

And this when using my dark theme…

Of course the archive linked to has the code for all the controls, but today I’m just focusing on the slider.

The slider hosted on the ToolStrip is this:

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace Romy.Controls
{
    /// <summary>A transparent Slider control for any of the containers in the ToolStrip family of controls.</summary>
    /// <remarks>This hosts my <see cref="Slider"/> control, not a standard <see cref="TrackBar"/>.</remarks>
    [ToolStripItemDesignerAvailability(ToolStripItemDesignerAvailability.None | ToolStripItemDesignerAvailability.ToolStrip | ToolStripItemDesignerAvailability.StatusStrip)]
    public class ToolStripSlider : ToolStripControlHost
    {
        #region Public Constructors

        public ToolStripSlider()
            : base(CreateControlInstance())
        { }

        #endregion Public Constructors

        #region Public Events

        public event EventHandler ValueChanged;

        #endregion Public Events

        #region Public Properties

        [Description("This property is not applicable for this type.")]
        public override Color BackColor
        {
            get => base.BackColor;
            set { }
        }

        [Description("This property is not applicable for this type.")]
        public override Image BackgroundImage
        {
            get => null;
            set => base.BackgroundImage = null;
        }

        [Description("This property is not applicable for this type.")]
        public override ImageLayout BackgroundImageLayout
        {
            get => base.BackgroundImageLayout;
            set { }
        }

        [Browsable(true), Category("Appearance"), Description("Changes the Silder's color scheme."), DefaultValue(SliderColorScheme.Grey)]
        public SliderColorScheme ColorScheme
        {
            get => SliderControl.ColorScheme;
            set => SliderControl.ColorScheme = value;
        }

        [Browsable(true), Category("Behavior"), DefaultValue(100),
        Description("The maximum value for the position of the slider on the track.")]
        public int Maximum
        {
            get => this.SliderControl.Maximum;
            set => this.SliderControl.Maximum = value;
        }

        [Browsable(true), Category("Behavior"), DefaultValue(0),
        Description("The minimum value for the position of the slider on the track.")]
        public int Minimum
        {
            get => this.SliderControl.Minimum;
            set => this.SliderControl.Minimum = value;
        }

        [Category("Behavior"), Description("Allows resetting the slider by pressing the space bar."), DefaultValue(true)]
        public bool ResetEnabled
        {
            get => this.SliderControl.ResetEnabled;
            set => this.SliderControl.ResetEnabled = value;
        }

        [Browsable(true), Category("Behavior"),
        Description("When the space bar is pressed while the slider has input focus, the slider will reset to this value.")]
        public int ResetValue
        {
            get => this.SliderControl.ResetValue;
            set => this.SliderControl.ResetValue = value;
        }

        public Slider SliderControl => Control as Slider;

        [Category("Behavior"), DefaultValue(false),
                        Description("If enabled, allows moving the slider with the left and right arrow keyboard keys.")]
        public bool UsesArrowKeys
        {
            get => this.SliderControl.UsesArrowKeys;
            set => this.SliderControl.UsesArrowKeys = value;
        }

        [Browsable(true), Category("Behavior"),
        Description("The position of the slider.")]
        public double Value
        {
            get => SliderControl.Value;
            set => SliderControl.Value = value;
        }

        #endregion Public Properties

        #region Protected Properties

        protected override Padding DefaultMargin => new Padding(0);

        protected override Padding DefaultPadding => new Padding(0);

        protected override Size DefaultSize => new Size(100, 20);

        #endregion Protected Properties

        #region Protected Methods

        protected override void OnSubscribeControlEvents(Control control)
        {
            base.OnSubscribeControlEvents(control);
            this.SliderControl.ValueChanged += new EventHandler(ToolStripSlider_ValueChanged);
        }

        protected override void OnUnsubscribeControlEvents(Control control)
        {
            base.OnUnsubscribeControlEvents(control);
            this.SliderControl.ValueChanged -= new EventHandler(ToolStripSlider_ValueChanged);
        }

        #endregion Protected Methods

        #region Private Methods

        private static Control CreateControlInstance() => new Slider(100, new Size(80, 20));

        private void ToolStripSlider_ValueChanged(object sender, EventArgs e) => ValueChanged?.Invoke(this, EventArgs.Empty);

        #endregion Private Methods
    }
}

And the control it hosts is this:

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Security.Permissions;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Romy.Controls
{
    public enum SliderColorScheme
    {
        Grey,
        Sepia
    }

    /// <summary>Specifies the visual state of a Slider thumb.</summary>
    /// <remarks>This is a copy of <see cref="System.Windows.Forms.VisualStyles.TrackBarThumbState"/>.</remarks>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue",
    Justification = "This is a copy of System.Windows.Forms.VisualStyles.TrackBarThumbState, which has the same issue.")]
    public enum SliderThumbState
    {
        /// <summary>The slider appears normal.</summary>
        Normal = 1,

        /// <summary>The thumb appears hot.</summary>
        Hot = 2,

        /// <summary>The thumb appears pressed.</summary>
        Pressed = 3,

        /// <summary>The thumb appears disabled.</summary>
        Disabled = 5,
    }

    /// <summary>Custom TrackBar-like control that supports a transparent BackColor.
    /// (This control currently only supports a horizontal track orientation.)</summary>
    /// <remarks><para>
    /// This was originally designed to be used only by this application's media player,
    /// in terms of its beheviour when clicked (described below) and its appearance (similar
    /// to the other controls on the media player). However, when a similar control was
    /// needed elsewhere, the <b>ColorScheme</b> property was added.</para>
    /// <para>
    /// Currently two schemes are supported: Grey is intended to have colours similar to
    /// a standard TrackBar (so that it doesn't look out of place when used with standard
    /// controls, e.g. on a ToolStrip), and Sepia is the theme used on the media player.
    /// </para><para>
    /// Note that the afore-mentioned ColorSchemes property has nothing to do with the
    /// Themes used throughout the application. I wrote this control long before thinking
    /// of implementing color-themes on the application level.</para>
    /// <para>
    /// See <see cref="ToolStripSlider"/> for a <b>Slider</b> that can be
    /// used on any of the containers in the ToolStrip family of controls. (Hosted by a
    /// <see cref="ToolStripControlHost"/>.)</para>
    /// <para>
    /// This is designed to behave the way I would have preferred the TrackBar control to
    /// behave, at least when the track is clicked. It doesn't have the SmallChange and
    /// LargeChange properties of a standard TrackBar. Clicking the track anywhere (rather
    /// than within a limited range of the slider's thumb button) sets the Value property,
    /// moves the thumb to the point clicked, and sets its state to pressed. Then with the
    /// mouse button held down, you can drag to slide the slider and seek to a new position.
    /// (and optionally make fine adjustments using the keyboard's arrow keys.)</para>
    /// <para>
    /// This was originally based on the TrackBarRenderer example in the Visual Studio 2010
    /// help, but has changed drastically since its initial implementation. Most notably, all
    /// references to the TrackBarRenderer were removed, and all drawing is hand-coded.</para>
    /// </remarks>
    public class Slider : Control
    {
        private double tickSpace;

        private double value;

        private const int TrackHeight = 4;

        private const int WM_KEYDOWN = 0x0100;

        private static readonly Lazy<Image> thumb = new Lazy<Image>(() => Properties.Resources.thumb);

        private static readonly Lazy<Image> thumbGrey = new Lazy<Image>(() => Properties.Resources.thumbGrey);

        private static readonly Lazy<Image> thumbGreyHot = new Lazy<Image>(() => Properties.Resources.thumbGreyHot);

        private static readonly Lazy<Image> thumbGreyPressed = new Lazy<Image>(() => Properties.Resources.thumbGreyPressed);

        private static readonly Lazy<Image> thumbHot = new Lazy<Image>(() => Properties.Resources.thumbHot);

        private static readonly Lazy<Image> thumbPressed = new Lazy<Image>(() => Properties.Resources.thumbPressed);

        private static readonly Lazy<Image> trackEdge = new Lazy<Image>(() => Properties.Resources.trackEdge);

        private static readonly Lazy<Image> trackGreyEdge = new Lazy<Image>(() => Properties.Resources.trackGreyEdge);

        private static readonly Lazy<Image> trackGreyMiddle = new Lazy<Image>(() => Properties.Resources.trackGreyMiddle);

        private static readonly Lazy<Image> trackMiddle = new Lazy<Image>(() => Properties.Resources.trackMiddle);

        private Rectangle thumbRectangle = new Rectangle();

        private Rectangle trackRectangle = new Rectangle();

        private SliderThumbState thumbState = SliderThumbState.Normal;

        public Slider(int maximum, Size sliderSize)
        {
            SetRange(0, maximum);
            this.SetBounds(this.Bounds.Left, this.Bounds.Top, sliderSize.Width, sliderSize.Height);

            Initialize();
        }

        public Slider() : this(100, new Size(100, 20))
        { }

        [Category("Behavior"), Description("Allows resetting the slider by pressing the space bar."), DefaultValue(true)]
        public bool ResetEnabled { get; set; } = true;

        [Browsable(false)]
        public bool ThumbClicked { get; private set; }

        /// <summary>Set true to allow the left and right arrow keyboard keys to fine-adjust the slider value.</summary>
        /// <remarks>Depending on where the control is situated, this may not be feasible. It is generally
        /// better to do this from a parent form's OnKeyDown handler, hence this defaults to false.</remarks>
        [Category("Behavior"), DefaultValue(false),
        Description("If enabled, allows moving the slider with the left and right arrow keyboard keys.")]
        public bool UsesArrowKeys { get; set; }

        [Description("This property is not applicable for this type.")]
        public override Color BackColor
        {
            get => base.BackColor;
            set { }
        }

        [Category("Behavior"), Description("The position of the slider.")]
        public double Value
        {
            get => value;
            set
            {
                this.value = value < this.Minimum ? this.Minimum : value > this.Maximum ? this.Maximum : value;
                UpdateThumbLocation();

                ValueChanged?.Invoke(this, EventArgs.Empty);
            }
        }

        [Description("This property is not applicable for this type.")]
        public override Image BackgroundImage
        {
            get => null;
            set => base.BackgroundImage = null;
        }

        public static Image Thumb => thumb.Value;

        public static Image ThumbGrey => thumbGrey.Value;

        public static Image ThumbGreyHot => thumbGreyHot.Value;

        public static Image ThumbGreyPressed => thumbGreyPressed.Value;

        public static Image ThumbHot => thumbHot.Value;

        public static Image ThumbPressed => thumbPressed.Value;

        public static Image TrackEdge => trackEdge.Value;

        public static Image TrackGreyEdge => trackGreyEdge.Value;

        public static Image TrackGreyMiddle => trackGreyMiddle.Value;

        public static Image TrackMiddle => trackMiddle.Value;

        [Description("This property is not applicable for this type.")]
        public override ImageLayout BackgroundImageLayout
        {
            get => base.BackgroundImageLayout;
            set { }
        }

        [Category("Behavior"), DefaultValue(100),
        Description("The maximum value for the position of the slider on the track.")]
        public int Maximum { get; set; }

        [Category("Behavior"),
        Description("The minimum value for the position of the slider on the track.")]
        public int Minimum { get; set; }

        [Category("Behavior"),
        Description("Pressing the space bar when ResetEnabled is set and the slider has input focus \"resets\" the slider to this value.")]
        public int ResetValue { get; set; }

        [Category("Appearance"), Description("Changes the Silder's color scheme."), DefaultValue(SliderColorScheme.Grey)]
        public SliderColorScheme ColorScheme { get; set; }

        protected SliderThumbState ThumbState
        {
            get => thumbState;
            set
            {
                thumbState = value;
                this.Invalidate();

                if (thumbState == SliderThumbState.Hot)
                    CheckThumbstate();
            }
        }

        public event EventHandler ValueChanged;

        /// <summary>Allows using the keyboard to modify the slider value.</summary>
        public override bool PreProcessMessage(ref Message msg)
        {
            new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();

            bool processedMessage = false;

            if (msg.Msg == WM_KEYDOWN)
            {
                Keys keys = (Keys)msg.WParam.ToInt32();

                switch (keys)
                {
                    case Keys.Left:
                        if (UsesArrowKeys)
                        {
                            if (this.Value > this.Minimum)
                                this.Value--;

                            processedMessage = true;
                        }
                        break;
                    case Keys.Right:
                        if (UsesArrowKeys)
                        {
                            if (this.Value < this.Maximum)
                                this.Value++;

                            processedMessage = true;
                        }
                        break;
                    case Keys.Space:
                        if (ResetEnabled)
                        {
                            this.Value = this.ResetValue;
                            processedMessage = true;
                        }
                        break;
                }
            }
            return processedMessage;
        }

        /// <summary>Sets the minimum and maximum values for a <see cref="Slider"/>.</summary>
        /// <remarks>Like the <see cref="TrackBar.SetRange(int, int)"/> method, you can
        /// use this method to set the entire range for the <see cref="Slider"/> at the same time.
        /// To set the minimum or maximum values individually, use the <see cref="Minimum"/>
        /// and <see cref="Maximum"/> properties. If the <i>minValue</i> parameter is greater
        /// than the <i>maxValue</i> parameter, <i>maxValue</i> is set equal to <i>minValue</i>.</remarks>
        /// <param name="minValue">The lower limit of the range of the slider. </param>
        /// <param name="maxValue">The upper limit of the range of the slider.</param>
        public void SetRange(int minValue, int maxValue)
        {
            if (minValue > maxValue)
                maxValue = minValue;

            this.Minimum = minValue;
            this.Maximum = maxValue;
        }

        protected override void OnLeave(EventArgs e)
        {
            StopSliding();
            base.OnLeave(e);
        }

        protected override void OnLostFocus(EventArgs e)
        {
            StopSliding();
            base.OnLostFocus(e);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                if (tickSpace > 0) // Set the value to the position clicked.
                {
                    int newValue = ValueFromXCoordinate(e);

                    if (newValue < this.Minimum)
                        newValue = this.Minimum;

                    if (newValue > this.Maximum)
                        newValue = this.Maximum;

                    Value = newValue;
                    ThumbClicked = true;
                    ThumbState = SliderThumbState.Pressed;
                }
            }
            base.OnMouseDown(e);
        }

        /// <summary>Grab focus to enable receiving key messages.</summary>
        protected override void OnMouseHover(EventArgs e)
        {
            Form form = this.FindForm();

            if (form == Form.ActiveForm)
                this.SelectAndFocus();

            base.OnMouseHover(e);
        }

        /// <summary>Track cursor movements.</summary>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            // The user is moving the thumb.
            if (ThumbClicked)
            {
                int newValue = ValueFromXCoordinate(e);

                if (newValue < this.Minimum)
                    newValue = this.Minimum;

                if (newValue > this.Maximum)
                    newValue = this.Maximum;

                Value = newValue;

                if (!thumbRectangle.Contains(e.Location))
                    this.OnLostFocus(EventArgs.Empty);
                else
                    ThumbState = SliderThumbState.Pressed;
            }
            else // The cursor is passing over the track.
                ThumbState = thumbRectangle.Contains(e.Location) ? SliderThumbState.Hot : SliderThumbState.Normal;

            base.OnMouseMove(e);
        }

        /// <summary>Redraw the slider thumb if the user has moved it.</summary>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (ThumbClicked)
            {
                if (e.Location.X > trackRectangle.X && e.Location.X < (trackRectangle.X + trackRectangle.Width - thumbRectangle.Width))
                {
                    ThumbClicked = false;
                    ThumbState = SliderThumbState.Hot;
                }

                ThumbClicked = false;
            }

            base.OnMouseUp(e);
        }

        /// <summary>Draw the slider.</summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            DrawHorizontalTrack(e);
            DrawVerticalThumb(e);

            base.OnPaint(e);
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            if (Width > 0)
                this.SetBounds(this.Bounds.Left, this.Bounds.Top, Width, Height);

            InitializeSlider();

            base.OnSizeChanged(e);
        }

        private int ValueFromXCoordinate(MouseEventArgs e)
        {
            // Note that this and XCoordinateFromValue are the same equation, just solving for a different value.
            return this.trackRectangle.Width == 0 ? 0 : (int)(((double)e.Location.X / this.trackRectangle.Width) * (this.Maximum - this.Minimum) + this.Minimum);
        }

        private int XCoordinateFromValue()
        {
            return this.Maximum == 0 || this.trackRectangle.Width == 0 ? 0 : (int)((double)(value - this.Minimum) / (this.Maximum - this.Minimum) * this.trackRectangle.Width);
        }

        /// <summary>Draws a horizontal slider track.</summary>
        private void DrawHorizontalTrack(PaintEventArgs e)
        {
            // Draw the left edge of the track. (Using my 1x4 pixel "edge" graphic.)
            e.Graphics.DrawImage(this.ColorScheme == SliderColorScheme.Sepia ? TrackEdge : TrackGreyEdge, trackRectangle.Left, trackRectangle.Top, 1, trackRectangle.Height);

            // Draw the middle of the track. (It simply tiles the 1x4 pixel middle graphic.)
            using (TextureBrush trackMiddleBrush = new TextureBrush(this.ColorScheme == SliderColorScheme.Sepia ? TrackMiddle : TrackGreyMiddle, WrapMode.Tile))
            {
                Rectangle rect = new Rectangle(trackRectangle.Left + 1, trackRectangle.Top, trackRectangle.Width - 2, trackRectangle.Height);
                e.Graphics.FillRectangle(trackMiddleBrush, rect);
            }

            // Draw the right edge of the track. (Draws the 1x4 pixel edge graphic again.)
            e.Graphics.DrawImage(this.ColorScheme == SliderColorScheme.Sepia ? TrackEdge : TrackGreyEdge, trackRectangle.Left + trackRectangle.Width - 1, trackRectangle.Top, 1, trackRectangle.Height);
        }

        /// <summary>Draws a vertical thumb button (the thumb button is vertical; the track is horizontal).
        /// This button is symmetrical, so the concept of bottom-pointing or top-pointing is irrelevant.</summary>
        private void DrawVerticalThumb(PaintEventArgs e)
        {
            if (this.ColorScheme == SliderColorScheme.Sepia)
                e.Graphics.DrawImage(ThumbState == SliderThumbState.Normal ? Thumb : ThumbState == SliderThumbState.Hot ? ThumbHot : ThumbPressed, new PointF(thumbRectangle.Left, thumbRectangle.Top - 1));
            else
                e.Graphics.DrawImage(ThumbState == SliderThumbState.Normal ? ThumbGrey : ThumbState == SliderThumbState.Hot ? ThumbGreyHot : ThumbGreyPressed, new PointF(thumbRectangle.Left, thumbRectangle.Top - 1));
        }

        private void Initialize()
        {
            this.SetStyle(ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.SupportsTransparentBackColor, true);
            this.BackColor = Color.Transparent;
            this.DoubleBuffered = true;
            this.ColorScheme = SliderColorScheme.Grey;
        }

        private void InitializeSlider()
        {
            this.Value = this.Minimum;

            using (Graphics g = this.CreateGraphics())
            {
                // Calculate the size of the track bar.
                trackRectangle = new Rectangle(
                    ClientRectangle.X + 4,
                    (ClientRectangle.Height - TrackHeight) / 2,
                    ClientRectangle.Width - 8,
                    TrackHeight);

                // Note: Leftmost position indicates value Minimum, not necessarily 0.
                tickSpace = (double)(trackRectangle.Width) / (Maximum - Minimum);

                thumbRectangle.Size = Thumb.Size;

                thumbRectangle.X = XCoordinateFromValue();
                thumbRectangle.Y = 2 + trackRectangle.Y - thumbRectangle.Size.Height / 2;
            }
        }

        private void StopSliding()
        {
            /* In case of a mouse down event with no corresponding mouse up; ensure the thumb isn't left in the pressed state.
             * Otherwise it could be very confusing to the user if they happen to move their mouse over the control. */
            if (ThumbClicked)
            {
                ThumbClicked = false;
                ThumbState = SliderThumbState.Normal;
            }
        }

        private void UpdateThumbLocation()
        {
            int x = XCoordinateFromValue();

            /* These adjustments allow for the width of the thumb itself, such that when you drag the slider to the min
             * and max positions, there is no "bouncing" affect as its position gets corrected, and there is no visible
             * edge of the track sticking out from behind the thumb when it is orientated in either min or max position. */
            int minValueAllowed = this.trackRectangle.Left - this.thumbRectangle.Width / 2;
            int maxAllowedValue = this.trackRectangle.Left + this.trackRectangle.Width - this.thumbRectangle.Width / 2;

            if (x < minValueAllowed)
                x = minValueAllowed;

            if (x > maxAllowedValue)
                x = maxAllowedValue;

            thumbRectangle.X = x;
            this.Invalidate();
        }

        /// <summary>Prevent the thumb button getting left in the hot state. (This can happen if the
        /// control loses focus without any relevant handlers being called, e.g. Alt+Tab.)</summary>
        private async void CheckThumbstate()
        {
            try
            {
                while (ThumbState == SliderThumbState.Hot && thumbRectangle.Contains(this.PointToClient(MousePosition)))
                {
                    await Task.Delay(TimeSpan.FromSeconds(1));
                }

                if (ThumbState == SliderThumbState.Hot)
                    ThumbState = SliderThumbState.Normal;
            }
            catch (ObjectDisposedException) { } // The owning form may be closed before our PointToClient call. (which will then cause an attempt to recreate the Handle of the already disposed form)
        }
    }
}
Posted in Programming | Tagged , , , , | Leave a comment

All the c# source code of the last few articles, and then some…

I guess it will never really be ready to share, so here it is… as is.

My old share that you’ll find links to in older posts, was shared on OneDrive. This is on Google drive. You’ll find two zip files in the shared directory. I’ll keep these updated until I put this in a shared repo.

  1. RomyView.zip contains the full source code.
  2. RomyView Release.zip contains the built binaries.

The name of the app is just a shortened version of my first name with “view” slapped onto it.

The reason it’s so large is that it includes all the code it references, and two static builds of ffmpeg.exe. If you use the video conversion part of the application, it then uses either the 32 bit or 64 bit executable depending on your machine.

The solution expects Visual Studio 2019, and .NET Framework version 4.8. I had to download 4.8 manually, because on my Windows 10 machines, the latest was 4.7.2. If everything is set up correctly on your machine, you should be able to run BuildAndBackup.cmd in the extracted directory, and that should then build the release, then create the two zip files that you downloaded as well as create a backup directory, where it puts the existing zip files. And obviously the solution should open and build with no issues in Visual Studio 2019.

The application itself launches with a splash screen that fades from completely opaque to invisible, before the browser form is shown. It supports being opened with a directory name parameter and only allows one instance. If you try to open it again, it will pass the command line on to the original instance. Then, it can open some image formats internally, view most videos (if you install Direct Show codecs like the KLite-Codec Pack), and it can open zip files (without password) internally. It also converts videos using ffmpeg, and in an unfortunate dodgy way, will allow you to do as many video conversions as you want simultaneously, each one showing a separate progress dialog which allows you to pause or resume the process, as well as set the process priority – even to realtime which will bring your machine to a grinding halt. Used unwisely, the application can turn your machine into a toaster.

The video player has some dodgy features, such as automatically persisting 10 playlists, and some repeat modes as well as starting and ending position for videos and slow-motion play… that only a meth-head could have written while tweaking on porn.

The image viewer can do some limited image editing, mostly applying image filters for contrast, hue, saturation, brightness, and so on. Most of the image filter dialogs have an implementation of my own pattern that can apply filters to all files in a directory asynchronously. So you can do crazy shit like, use ffmpeg to extract all frames of a video, then apply the same image filters to all the frames, then re-encode all those frames back to a video. Actually I used to do the last part with a batch file – I never added the option to select separate audio and video streams and join them with my ffmpeg wrapper.

Everything is custom-drawn, even the toolstrip and menustrips. So hovering the mouse over menu items draws a nice gradient-filled rounded rectangle like selected thumbnails.

I did some crazy shit with drawing shadows for all the windows controls. For example, this dialog… Not a very intelligent batch rename dialog, but notice the thumbnail control on the right is the same as that used in the main form, plus all the panels and buttons on this dialog have shadows.

SNAGHTML34ec01fa

Here’s a screenshot that shows the custom-drawn context menu: (Open in new tab to see it properly.)

image

Thumbnails are cached to binary on the filesystem, while thumbnail controls themselves are reused via an object pool.

There’s some good code here, and some… well, dodgy code. Mostly there’s code that I’m proud of and it pisses me off that nobody ever uses it.

Some screenshots of the application…

The main form:

SNAGHTML34d553c3

The zip viewer:

SNAGHTML34d5cc3f

The image viewer, viewing a screenshot of the post that got me banned from Facebook:

SNAGHTML34de3274

The video convertor, having selected a video of my two beautiful children:

SNAGHTML34dcbcad

The video player playing a “Satanic Winnie the Pooh” video

If you don’t find this funny, I feel sorry for you.

image

You don’t wanna know how long I tweaked on the images and custom slider to make this form look alright, back when I was still a meth-head. Here’s the same form when my app is using its dark theme…

The controls shown on this form, all are all custom ToolStripButtons and a slider, with images carefully created by hand in Photoshop. From left to right, they are: play, pause, stop, fast-forward, step back (hidden in half-size mode due to the window size and overflow settings), step forward, next file, and previous file; then play-rate, zoom mode, repeat (folder) mode , time/duration label, volume/mute button, and custom slider.

image

Since the code is now shared, I can write other posts that refer to bits of it and link back here.

Posted in Programming | Tagged , | Leave a comment

Ever wondered how code can estimate the size of an image to be saved?

This is an idea I came up with a few years ago, while high on meth, so how clever it really is, is probably debatable.

I’d noticed how applications that can save images always have this feature where they tell you the size that the image will be on disk, and decided to implement it myself. Here’s how I did it:

  1. Write a method that supports saving an image in jpeg format, and specifying the quality.
  2. Call the method in a background thread, triggered whenever anyone changes the quality.
  3. Display the size of the image you saved in the background as the “estimated” size.

So all that code does is use this extension, and display the array length (in bytes) as the estimated size.

/// <summary>Convert an image to a byte array in jpeg format at the quality specified. This is useful
/// for saving in advance, and displaying the "estimated" file size (i.e. the array length).</summary>
public static byte[] ToArray(this Image image, long quality)
{
    if (image == null)
        throw new ArgumentNullException(nameof(image));

    using (MemoryStream stream = new MemoryStream())
    {
        image.SaveJpeg(stream, quality);
        return stream.ToArray();
    }
}

I’m not sharing the calling code, but here is an example that uses it… It’s not a standard dialog but my custom “Save as” dialog, which uses my horrible shell treeview mentioned in the previous post, as well as my thumbnail control and custom FlowLayoutPanel – and the part relevant here… my custom slider control that allows the user to change the desired image quality. Whenever the user changes the quality (and then stops playing with the slider for the time interval I define as having finished), a background thread is fired off to save the image at that quality using the code shown above, and the file size label is updated to reflect the size of the image bytes. It doesn’t throw away the image byte array saved in the background, but caches it, so if the user clicks save and there is a precached image, the save action appears to be instant, because all it needs to do at that point is save the image bytes to a new file stream.

SNAGHTML338a6c18

The code to format the size was shared the other day, in this post. But I do find it amusing that the best way to “estimate” how big an image conversion will be, is to actually just do the conversion. I’ve used the same technique elsewhere… any place where some action can be performed on an object and we have default or persisted options to use, just go ahead and perform the action in a background thread and cache the result. If the user cancels, discard the precached result (and clean up any memory used); but if the user proceeds, using the cached result makes the application seem faster. (The background work can be done via an async method that returns a Task, and takes a CancellationTokenSource parameter.  Thus if the user cancels the operation completely and that task is still in flight, cancel it.)

Oops… I forgot that SaveJpeg was one of my extensions too. Here’s my whole class of image extensions. (Disclaimer: All the code I’m sharing today was written years ago while I was high on methamphetamine, tweaking my ass off and writing code all night every night. Use at your own risk and if you see anything dumb in the code, feel free to fix it and comment here. But yeah… I really was out of my mind on meth when I wrote this.)

using Romy.Core;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace System.Drawing
{
    /// <summary>Several synchronous and asynchronous extension methods
    /// to make saving and converting images a little easier.</summary>
    public static class ImageExtensions
    {
        #region Private Fields

        /// <summary>Cached encoder for improved performance.</summary>
        private static readonly Lazy<ImageCodecInfo> jpegEncoder = new Lazy<ImageCodecInfo>(() => ImageCodecInfo.GetImageDecoders().FirstOrDefault(j => j.FormatID == ImageFormat.Jpeg.Guid));

        #endregion Private Fields

        #region Internal Properties

        internal static ImageCodecInfo JpegEncoder => jpegEncoder.Value;

        #endregion Internal Properties

        #region Public Methods

        /// <summary>Creates a 24 bit-per-pixel copy of the source image.</summary>
        public static Image CopyImage(this Image image) => CopyImage(image, PixelFormat.Format24bppRgb);

        /// <summary>Creates a copy of the source image with the specified pixel format.</summary><remarks>
        /// This can also be achieved with the <see cref="Bitmap.Clone(int, int, PixelFormat)"/>
        /// overload, but I have had issues with that method.</remarks>
        public static Image CopyImage(this Image image, PixelFormat format)
        {
            if (image == null)
                throw new ArgumentNullException(nameof(image));

            // Don't try to draw a new Bitmap with an indexed pixel format.
            if (format == PixelFormat.Format1bppIndexed || format == PixelFormat.Format4bppIndexed || format == PixelFormat.Format8bppIndexed || format == PixelFormat.Indexed)
                return (image as Bitmap).Clone(new Rectangle(0, 0, image.Width, image.Height), format);

            Image result = null;
            try
            {
                result = new Bitmap(image.Width, image.Height, format);

                using (Graphics graphics = Graphics.FromImage(result))
                {
                    graphics.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic;
                    graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias;
                    graphics.CompositingQuality = Drawing2D.CompositingQuality.HighQuality;

                    graphics.DrawImage(image, 0, 0, result.Width, result.Height);
                }
            }
            catch
            {
                if (result != null)
                    result.Dispose();

                throw;
            }
            return result;
        }

        public static void GetFormatAndSave(this Image image, string filename)
        {
            if (image == null)
                throw new ArgumentNullException(nameof(image));

            if (filename == null)
                throw new ArgumentNullException(nameof(filename));

            using (FileStream stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, Constants.BufferSize, false))
            {
                /* This makes use of my existing logic in ImageManager, to figure out what image format
                 * should be used to save the image to a stream, hopefully without throwing an exception.
                 * Of course, any formats I don't know how to handle will probably throw an exception. */
                ImageManager manager = new ImageManager(image, filename);

                using (MemoryStream memStream = new MemoryStream(manager.Data.ToArray()))
                {
                    memStream.CopyTo(stream);
                }
            }
        }

        /// <summary>Asynchronously saves the image to the specified file, after resolving the format to use.</summary>
        /// <param name="image">The image to save.</param>
        /// <param name="filename">The full path and file name to which the image must be saved.</param>
        public static async Task GetFormatAndSaveAsync(this Image image, string filename)
        {
            if (image == null)
                throw new ArgumentNullException(nameof(image));

            if (filename == null)
                throw new ArgumentNullException(nameof(filename));

            using (FileStream stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, Constants.BufferSize, true))
            {
                /* This makes use of my existing logic in ImageManager, to figure out what image format
                 * should be used to save the image to a stream without throwing an exception.
                 * Of course, any formats I don't know how to handle will probably throw an exception. */
                ImageManager manager = new ImageManager(image, filename);

                using (MemoryStream memStream = new MemoryStream(manager.Data.ToArray()))
                {
                    await memStream.CopyToStreamAsync(stream);
                }
            }
        }

        /// <summary>Resizes an image, optionally maintaining width:height ratios.</summary>
        /// <param name="image">The <see cref="Image"/> that you wish to resize.</param>
        /// <param name="width">The desired width of the resulting image.</param>
        /// <param name="height">The desired height of the resulting image.</param>
        /// <param name="maintainAspectRatio"><b>True</b> to maintain aspect ratio,
        /// otherwise <b>false</b>. This defaults to <b>true</b>.</param>
        /// <returns>The resulting resized <see cref="Image"/> object.</returns>
        public static Image Resize(this Image image, int width, int height, bool maintainAspectRatio = true)
        {
            if (image == null)
                throw new ArgumentNullException(nameof(image));

            if (maintainAspectRatio)
            {
                // calculate resize ratio
                double ratio = (double)width / image.Width;

                if (ratio * image.Height > height)
                    ratio = (double)height / image.Height;

                width = (int)Math.Round(ratio * image.Width, MidpointRounding.AwayFromZero);
                height = (int)Math.Round(ratio * image.Height, MidpointRounding.AwayFromZero);
            }

            Bitmap bmp = null;
            try
            {
                bmp = new Bitmap(width, height);

                using (Graphics g = Graphics.FromImage(bmp))
                {
                    g.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic;
                    g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality;
                    g.CompositingQuality = Drawing2D.CompositingQuality.HighQuality;
                    g.DrawImage(image, 0, 0, width, height);
                }

                return bmp;
            }
            catch
            {
                if (bmp != null)
                    bmp.Dispose();
                throw;
            }
        }

        /// <summary>Save the specified image to the specified stream, in jpeg format, at the quality specified.
        /// </summary><remarks>Since this method is called frequently, the jpeg encoder is cached.</remarks>
        /// <param name="image">The image to save.</param>
        /// <param name="stream">The stream to which the image will be saved.</param>
        /// <param name="quality">The quality to use for the saved image.
        /// (where higher quality means less compression, and a larger file)</param>
        public static void SaveJpeg(this Image image, Stream stream, long quality = 80L)
        {
            if (image == null)
                throw new ArgumentNullException(nameof(image));

            if (stream == null)
                throw new ArgumentNullException(nameof(stream));

            using (Bitmap tempBitmap = image.CopyImage(image.PixelFormat) as Bitmap)
            {
                using (EncoderParameters encoderParameters = new EncoderParameters(1))
                {
                    encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality);
                    tempBitmap.Save(stream, JpegEncoder, encoderParameters);
                }
            }
        }

        /// <summary>Save the specified image to the file specified, at the quality specified.</summary>
        /// <param name="image">The image to save.</param>
        /// <param name="filename">The full path and file name to which the image must be saved.</param>
        /// <param name="quality">The quality to use for the saved image.
        /// (where higher quality means less compression, and a larger file)</param>
        public static void SaveJpeg(this Image image, string filename, long quality = 80L)
        {
            if (filename == null)
                throw new ArgumentNullException(nameof(filename));

            using (FileStream stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, Constants.BufferSize, true))
            {
                image.SaveJpeg(stream, quality);
            }
        }

        /// <summary>Asnchronously save the specified image to the file specified, at the quality specified.</summary>
        /// <param name="image">The image to save.</param>
        /// <param name="filename">The full path and file name to which the image must be saved.</param>
        /// <param name="quality">The quality to use for the saved image.
        /// (where higher quality means less compression, and a larger file)</param>
        public static async Task SaveJpegAsync(this Image image, string filename, long quality = 80L)
        {
            if (image == null)
                throw new ArgumentNullException(nameof(image));

            if (filename == null)
                throw new ArgumentNullException(nameof(filename));

            using (MemoryStream memStream = new MemoryStream())
            {
                image.SaveJpeg(memStream, quality);

                using (FileStream stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, Constants.BufferSize, true))
                {
                    memStream.Position = 0;
                    await memStream.CopyToStreamAsync(stream);
                }
            }
        }

        /// <summary>Asynchronously save the specified image to the specified stream,
        /// in jpeg format, at the quality specified.</summary>
        /// <param name="image">The image to save.</param>
        /// <param name="stream">The stream to which the image will be saved.</param>
        /// <param name="quality">The quality to use for the saved image.
        /// (where higher quality means less compression, and a larger file)</param>
        public static async Task SaveJpegAsync(this Image image, Stream stream, long quality = 80L)
        {
            if (image == null)
                throw new ArgumentNullException(nameof(image));

            if (stream == null)
                throw new ArgumentNullException(nameof(stream));

            using (MemoryStream memStream = new MemoryStream())
            {
                image.SaveJpeg(memStream, quality);
                memStream.Position = 0;
                await memStream.CopyToStreamAsync(stream);
            }
        }

        /// <summary>Overwrites an existing jpeg file after calculating the required quality
        /// to save it as close to, but not smaller than the existing file size.</summary>
        public static async Task<long> SaveJpegPreserveFileSizeAsync(this Image modified, string filename)
        {
            if (modified == null)
                throw new ArgumentNullException(nameof(modified));

            if (filename == null)
                throw new ArgumentNullException(nameof(filename));

            long existingFileSize = new FileInfo(filename).Length;

            byte[] current = null, previous = null;

            for (long quality = 99L; quality > 0L; quality--)
            {
                if (current != null)
                    previous = current;

                /* My ToArray extension method saves the jpeg to memory at the specified quality, and returns the memory
                 * as a byte array. Loop until we find a file size less than the target size; then use previous value. */
                current = modified.ToArray(quality);

                if (current.Length < existingFileSize)
                {
                    if (previous == null) // This can only happen when quality == 99 and previous is uninitialized.
                        previous = modified.ToArray(++quality);

                    using (MemoryStream stream = new MemoryStream(previous))
                    {
                        using (FileStream destStream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, Constants.BufferSize, true))
                        {
                            await stream.CopyToStreamAsync(destStream);
                        }
                    }
                    return quality;
                }
            }

            // It should never get here, but if it does, save with no compression.
            await modified.SaveJpegAsync(filename, 100L);
            return 100L;
        }

        /// <summary>Convert an image to a byte array in png format.</summary>
        public static byte[] ToArray(this Image image)
        {
            if (image == null)
                throw new ArgumentNullException(nameof(image));

            using (MemoryStream stream = new MemoryStream())
            {
                image = image.CopyImage(PixelFormat.Format32bppArgb);
                image.Save(stream, ImageFormat.Png);
                return stream.ToArray();
            }
        }

        /// <summary>Convert an image to a byte array in jpeg format at the quality specified. This is useful
        /// for saving in advance, and displaying the "estimated" file size (i.e. the array length).</summary>
        public static byte[] ToArray(this Image image, long quality)
        {
            if (image == null)
                throw new ArgumentNullException(nameof(image));

            using (MemoryStream stream = new MemoryStream())
            {
                image.SaveJpeg(stream, quality);
                return stream.ToArray();
            }
        }

        #endregion Public Methods
    }
}

One or two of those extensions use my ImageManager class, which is an internal class I use to manage references to images, and manage the types of images my application supports: (I’m not including my IO static class, which has methods like IO.IsJPeg and so on. Those just check the files based on file extension… because including that class will open yet another can of worms and then I’ll need to include other code.)

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;

namespace Romy.Core
{
    /// <summary>A type to manage image references so that they can safely be manipulated asynchronously.</summary>
    /// <remarks><para>
    /// The intention is to solve the issue of accessing a GDI+ image reference asynchronously, and having it
    /// ripped out from under you unexpectedly.</para>
    /// <para>
    /// This gets tricky in async methods, despite "protecting" the image instances in either try...finally blocks
    /// or using directives. While the compiler-generated code saves state, returns and later resumes execution via
    /// a state machine, image references may be disposed on the main thread, leading to unexpected unhandled
    /// exceptions in our processing threads, possibly severe enough even to take down the application (although
    /// generally just annoying: "A generic error occurred in GDI+")</para>
    /// <para>
    /// Rather than holding a reference to the image itself, this type simply stores the image bytes. Thus each
    /// time the <see cref="ToImage()"/> or <seealso cref="ToBitmap()"/> method is called,
    /// the caller will get a unique image from the original image data.</para>
    /// <para>
    /// This type can slow performance if used excessively or when it is unnecessary, but the overhead of the
    /// mechanism used to create the unique image instances, when used as intended from async Tasks, is negligable.</para>
    /// <para>
    /// This type doesn't really do anything smart like create an actual image that can be manipulated. It just caches
    /// the managed image bytes. (And effectively manages <i>references</i> to the image. (I do this because I know
    /// nothing of graphics processing, but wanted a way to safely pass references to a managed image in async threads,
    /// even though I may dispose the original image.) For a similar type that is more useful (to developers who know
    /// what they are doing w.r.t. graphics programming), I suggest using something like the AForge.Net
    /// <see cref="AForge.Imaging.UnmanagedImage"/>.</para></remarks>
    public sealed class ImageManager
    {
        #region Private Fields

        /// <summary>This is used to test if an image is an animated GIF file. If so, we then load
        /// the image using an alternative method so as not to lose subsequent frames.</summary>
        private static readonly Lazy<ImageCodecInfo> gifEncoder = new Lazy<ImageCodecInfo>(() => ImageCodecInfo.GetImageDecoders().FirstOrDefault(g => g.FormatID == ImageFormat.Gif.Guid));

        /// <summary>Holds the actual image data.</summary>
        private byte[] data;

        #endregion Private Fields

        #region Public Constructors

        public ImageManager(Image image, string filename) => Initialize(image, filename);

        /// <summary>Intended use of this type is to be passed an Image,
        /// but if all we have is a file name, try to load it.</summary>
        /// <remarks>It will load the image for a supported image type
        /// from disk, or try to load a thumbnail image for a directory
        /// name or video file.</remarks>
        public ImageManager(string filename)
        {
            Image image = null;
            try
            {
                if (IO.IsImageFile(filename))
                {
                    image = ImageFile.OpenBitmap(filename);
                }
                else if (Directory.Exists(filename) || IO.IsMediaFile(filename))
                    // Load the thumbnail image, if we can. (256x256)
                    image = ShellItemImageFactory.GetImage(filename);

                if (image != null)
                    Initialize(image, filename);
                else
                    throw new InvalidOperationException(string.Format("Unable to load an image from '{0}'.", filename));
            }
            finally
            {
                if (image != null)
                    image.Dispose();
            }
        }

        #endregion Public Constructors

        #region Public Properties

        public IEnumerable<byte> Data => data;

        public ImageFormat Format { get; private set; }

        #endregion Public Properties

        #region Public Methods

        public Bitmap ToBitmap() => ToImage() as Bitmap;

        public Image ToImage()
        {
            using (MemoryStream stream = new MemoryStream(data))
            {
                return new Bitmap(stream);
            }
        }

        #endregion Public Methods

        #region Private Methods

        private static ImageFormat GetImageFormat(Image image, string filename)
        {
            ImageFormat format = image.RawFormat;

            if (IO.IsJpeg(filename))
                format = ImageFormat.Jpeg;
            else if (IO.IsPng(filename) || Directory.Exists(filename) || IO.IsMediaFile(filename))
                format = ImageFormat.Png;
            else if (IO.IsTif(filename))
                format = ImageFormat.Tiff;
            else if (IO.IsIcon(filename))
                format = ImageFormat.Icon;
            else if (IO.IsGif(filename))
                format = ImageFormat.Gif;
            else if (IO.IsBmp(filename))
                format = ImageFormat.Bmp;

            return format;
        }

        private void Initialize(Image image, string filename)
        {
            /* Although one might expect saving the image data to a stream to be
             * as simple as specifying the image with it's raw format, doing so
             * throws an exception. Thus to save it successfully, we must check
             * for image formats we support, and handle each format appropriately. */
            using (MemoryStream stream = new MemoryStream())
            {
                Format = GetImageFormat(image, filename);

                /* In each case, save the image to our internal stream in the
                 * appropriate format, so that the bytes can be stored. */
                if (Format == ImageFormat.Jpeg)
                {
                    image.SaveJpeg(stream, 100L);
                }
                else if (Format == ImageFormat.Icon)
                {
                    /* My IconFileWriter only supports writing png images to the images collection of icon files.
                     * This is probably the safest way to handle all icons. (But most modern icons *are* png anyway.) */
                    image = image.CopyImage(PixelFormat.Format32bppArgb);
                    image.Save(stream, ImageFormat.Png);
                }
                else if (Format == ImageFormat.Gif)
                {
                    if (IO.IsGif(filename) && File.Exists(filename))
                    {
                        /* This IF block only applies to GIF images that we load from file.
                         * It does not apply when we are converting something else to GIF. */
                        ImageCodecInfo encoder = gifEncoder.Value;

                        if (encoder != null && encoder.FormatID == image.RawFormat.Guid && ImageAnimator.CanAnimate(image))
                        {
                            /* This will load the GIF file properly with all it's
                             * frames. Otherwise we only get the first frame. */
                            image = (Bitmap)Image.FromFile(filename);
                        }
                    }
                    image = image.CopyImage(PixelFormat.Format16bppRgb565);
                    image.Save(stream, ImageFormat.Gif);
                }
                else if (Format == ImageFormat.Tiff)
                {
                    image = image.CopyImage(PixelFormat.Format24bppRgb);
                    image.Save(stream, ImageFormat.Tiff);
                }
                else if (Format == ImageFormat.Bmp)
                {
                    image.CopyImage(PixelFormat.Format32bppArgb).Save(stream, ImageFormat.Bmp);
                }
                else
                    // Ignore the RawFormat completely and force png format; it's only going to throw otherwise.
                    image.CopyImage(PixelFormat.Format32bppArgb).Save(stream, ImageFormat.Png);

                // Store the image byte data.
                data = stream.ToArray();
            }
        }

        #endregion Private Methods
    }
}

And lastly, this is the code I use to load image files from disk: (You can remove the bits that refer to PSD files.)

using Photoshop;
using System;
using System.Drawing;
using System.IO;
using System.Threading.Tasks;

namespace Romy.Core
{
    public static class ImageFile
    {
        /// <summary>Opens and returns an in-memory copy of the specified image, so as not to lock the file.</summary>
        public static Bitmap OpenBitmap(string filename)
        {
            Bitmap image = null;

            void disposeImageOnError()
            {
                if (image != null)
                {
                    image.Dispose();
                    image = null;
                }
            }

            try
            {
                /* For animated GIF files, opening them any other way will 
                 * result in an error when trying to animate them later. */
                if (string.Compare(Path.GetExtension(filename), ".gif", StringComparison.InvariantCultureIgnoreCase) == 0)
                    image = Image.FromFile(filename) as Bitmap;
                else
                {
                    using (MemoryStream stream = new MemoryStream(File.ReadAllBytes(filename)))
                    {
                        if (string.Compare(Path.GetExtension(filename), ".psd", StringComparison.InvariantCultureIgnoreCase) != 0)
                            image = new Bitmap(stream);
                        else
                        {
                            using (PsdFile psdFile = new PsdFile(stream))
                                image = psdFile.CompositImage.Clone() as Bitmap;
                        }
                    }
                }
            }
            catch (IOException) { disposeImageOnError(); }
            catch (UnauthorizedAccessException) { disposeImageOnError(); }
            catch (Exception ex)
            {
                ex.Log();
                disposeImageOnError();
            }

            return image;
        }

        /// <summary>Opens and returns a Bitmap using asynchronous IO, using the Task-based Asynchronous Pattern (TAP) pattern.
        public static async Task<Bitmap> OpenBitmapAsync(string filename)
        {
            Bitmap image = null;

            try
            {
                /* For animated GIF files, opening them any other way will 
                 * result in an error when trying to animate them later. */
                if (string.Compare(Path.GetExtension(filename), ".gif", StringComparison.InvariantCultureIgnoreCase) == 0)
                    image = Image.FromFile(filename) as Bitmap;
                else
                {
                    using (MemoryStream memStream = new MemoryStream(await FileAsync.ReadAllBytesAsync(filename, Constants.LargeBufferSize)))
                    {
                        if (memStream.Length > 0) // My ReadAllBytesAsync method can return an empty array. Via Enumerable.Empty<byte>().ToArray()
                        {
                            if (string.Compare(Path.GetExtension(filename), ".psd", StringComparison.InvariantCultureIgnoreCase) != 0)
                                image = new Bitmap(memStream);
                            else
                            {
                                using (PsdFile psdFile = new PsdFile(memStream))
                                    image = psdFile.CompositImage.Clone() as Bitmap;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                ex.Log();

                if (image != null)
                {
                    image.Dispose();
                    image = null;
                }
            }

            return image;
        }
    }
}
Posted in Programming | Tagged , | Leave a comment

Still not ready to share the full source code

I mentioned yesterday that I intend sharing the full source code of my mostly experimental application. But I’m not quite ready yet. I’ve updated the code base fully now, for VS 2019, and addressed the worst bugs that I knew about. So it’s more or less ready to share. But the one thing missing is that I would like to try changing all references to MemoryStream to use The Bing Team’s RecyclableMemoryStream instead.

That’s an experimental change but one that I think is worth trying. Also my code no longer uses my own custom TaskScheduler by default, although it’s options still refer to properties of that scheduler. Rather it uses a scheduler from Microsoft sample code, but one that’s much better (faster) at populating thumbnails (when uncached) when browsing directories that contain many files. Also my caching has been improved. Browsing a directory when the images are cached results in them displaying instantly, always.

And lastly, there is one buggy control I may never fix… that shitty treeview on the left in the image below. It looks nice and seems similar to the Windows explorer one, and it uses all the right shell methods to list the directories there, but the code is nasty… and even has hard-coded English names for special folders. The whole control really needs to be replaced or rewritten, and yet it drives the application’s navigation so the whole app is tightly coupled with this horrible control.

RomyView

Posted in Programming | Tagged | Leave a comment

Extracting shell images for files and directories in c#

I did share my application’s full source code years ago, but that was way out of date and I should update it at some point. Anyway… my app is mostly an image viewer like the old ACDSee application, and it displays the files it supports in a Windows explorer like interface. So it uses the Windows shell interfaces to fetch thumbnail images for the files it supports, and also for directories. The main form looks like this:

Browser form

You’ll have to open that in a new tab to see it properly. Ignore the fact that I’m using my own custom drawn controls (main panel is a custom FlowlayoutPanel, thumbnails are a custom control, and images are drawn everywhere, including the thumbnails, on a custom control rather than the PictureBox), with a gradient-filled rounded rectangle for the selected file. Sharing all the code to do that would involve far too much writing for my liking. So today I’m just sharing how I got those images from the Windows shell.

What it comes down to is, the code gets the images one of two ways:

  1. In Windows 7 and up (I’m on Windows 10 Pro here), it uses IShellItemImageFactory.GetImage.
  2. In Windows XP, it uses the much slower IExtractImage.Extract.

Either way, it ends up having to work around opaque images with solid colour background instead of transparent ones for directories, and does so via an extension method that uses an image library to flood fill them, and then trims the outside colour to force transparency. (Not a perfect solution, but a workaround. It fails when directory images, with white background, have white inside that’s connected to the outside of the image, because in that case the code can sometimes set parts of the inner image transparent too.) I’ll share that code as well, but not the image library. Note that although I use the AForge.NET framework, which is available via Nuget, I use a slightly customized version of their source code because mine needed to be strongly signed.

Here’s another view of the main form with a directory thumbnail selected and a mouse-over effect for a file, so you can see the workaround for opaque directory images actually works… If it didn’t work, that gradient-filled rounded rectangle drawn to show the selected thumbnail wouldn’t look quite the way it does. A complete failure would leave a solid white background around the outside of the “folder”, while over-aggressive filling and “trimming” would remove the white area inside the image, and you’d see the selection gradient painted inside the image.

Browser form

On with the code.

Here is my wrapper for IShellItemImageFactory:

using System.Drawing;
using System.Security;

namespace System.Runtime.InteropServices.ComTypes
{
    internal enum SIGDN : uint
    {
        NORMALDISPLAY = 0,
        PARENTRELATIVEPARSING = 0x80018001,
        PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
        DESKTOPABSOLUTEPARSING = 0x80028000,
        PARENTRELATIVEEDITING = 0x80031001,
        DESKTOPABSOLUTEEDITING = 0x8004c000,
        FILESYSPATH = 0x80058000,
        URL = 0x80068000
    }

    [Flags]
    internal enum SIIGBF
    {
        SIIGBF_RESIZETOFIT = 0x00,
        SIIGBF_BIGGERSIZEOK = 0x01,
        SIIGBF_MEMORYONLY = 0x02,
        SIIGBF_ICONONLY = 0x04,
        SIIGBF_THUMBNAILONLY = 0x08,
        SIIGBF_INCACHEONLY = 0x10,
    }

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
        Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
    [SuppressUnmanagedCodeSecurity]
    internal interface IShellItem
    {
        void BindToHandler(IntPtr pbc,
            [MarshalAs(UnmanagedType.LPStruct)]Guid bhid,
            [MarshalAs(UnmanagedType.LPStruct)]Guid riid,
            out IntPtr ppv);

        void GetParent(out IShellItem ppsi);

        void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName);

        void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);

        void Compare(IShellItem psi, uint hint, out int piOrder);
    };

    [ComImport, Guid("bcc18b79-ba16-442f-80c4-8a59c30c463b"),
        InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [SuppressUnmanagedCodeSecurity]
    internal interface IShellItemImageFactory
    {
        [PreserveSig]
        HResult GetImage(
        [In, MarshalAs(UnmanagedType.Struct)] SIZE size,
        [In] SIIGBF flags,
        [Out] out IntPtr phbm);
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct SIZE
    {
        public SIZE(int cx, int cy)
        {
            this.cx = cx;
            this.cy = cy;
        }

        public int cx;

        public int cy;
    }

    /// <summary>Extracts thumbnail images using Windows Shell interface methods.</summary>
    /// <remarks>
    /// In Windows Vista/7 up, the IShellItemImageFactory interface is used to extract the image,
    /// while in older Windows operating systems such as Windows XP, the IExtractImage interface
    /// is used instead.</remarks>
    public static class ShellItemImageFactory
    {
        #region Private Fields

        private static Guid IID_ShellItem = new Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe");

        #endregion Private Fields

        #region Private Enums

        private enum ShellImageSize
        {
            Small = 16,
            Medium = 48,
            Large = 96,
            ExtraLarge = 256
        }

        #endregion Private Enums

        #region Public Properties

        public static bool SupportsShellItemImageFactory => (Environment.OSVersion.Version.Major >= 6);

        #endregion Public Properties

        #region Public Methods

        public static Bitmap GetImage(string filename)
        {
            if (filename == null)
                throw new ArgumentNullException(nameof(filename));

            if (!SupportsShellItemImageFactory)
            {
                // Use my IExtractImage wrapper instead
                return ExtractImage.ExtractThumbnail(filename);
            }

            if (NativeMethods.SHCreateItemFromParsingName(filename, IntPtr.Zero, IID_ShellItem, out IShellItem ppsi) == HResult.S_OK)
            {
                HResult hRes = ((IShellItemImageFactory)ppsi).GetImage(new SIZE((int)ShellImageSize.ExtraLarge, (int)ShellImageSize.ExtraLarge), SIIGBF.SIIGBF_BIGGERSIZEOK, out IntPtr hbitmap);

                if (hRes == HResult.S_OK)
                {
                    try
                    {
                        return Romy.Core.Shell.Common.FixShellBitmap(filename, Image.FromHbitmap(hbitmap));
                        //return Bitmap.FromHbitmap(hbitmap);
                    }
                    finally { NativeMethods.DeleteObject(hbitmap); }
                }
                else { Marshal.ThrowExceptionForHR((int)hRes); }
            }

            return null;
        }

        #endregion Public Methods

        #region Private Classes

        [SuppressUnmanagedCodeSecurity]
        private static class NativeMethods
        {
            #region Public Methods

            [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool DeleteObject(IntPtr hObject);

            [DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = true)]
            public static extern HResult SHCreateItemFromParsingName(
                [In][MarshalAs(UnmanagedType.LPWStr)] string pszPath,
                [In] IntPtr pbc,
                [In][MarshalAs(UnmanagedType.LPStruct)] Guid riid,
                [Out][MarshalAs(UnmanagedType.Interface, IidParameterIndex = 2)] out IShellItem ppv);
            #endregion Public Methods
        }

        #endregion Private Classes
    }
}

Here’s my wrapper for IExtractImage:

using System.Drawing;
using System.IO;
using System.Runtime.CompilerServices;
using System.Security;
using System.Text;

namespace System.Runtime.InteropServices.ComTypes
{
    /// <summary>Extracts thumbnail images using the IExtractImage interface. Windows XP.</summary>
    /// <remarks><para>
    /// In Windows Vista and later, the <b>IShellItemImageFactory</b> interface should be used instead,
    /// as used by my <see cref="ShellItemImageFactory"/> type. This is the old way of extracting shell
    /// images. It is slower and the implementation is non-trivial. In fact, most implementations of it
    /// that can be found online are copies of the same poorly written code, and perform needless extra
    /// enumeration. (While fetching each individual thumbnail in a directory, for each file they
    /// enumerate the whole directory over again. I have seen this error in all examples online.)</para>
    /// <para>
    /// Also, this extracts thumbnails using the older Windows XP logic. i.e. directories only get
    /// thumbnails if they contain files that are thumbnailed, such as images and videos.</para></remarks>
    public static class ExtractImage
    {
        #region Private Fields

        private static Guid IID_IExtractImage = new Guid("{BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}");

        private static Guid IID_ShellFolder = new Guid("000214E6-0000-0000-C000-000000000046");

        #endregion Private Fields

        #region Private Enums

        private enum EIEIFLAG
        {
            IEIFLAG_ASYNC = 1,
            IEIFLAG_CACHE = 2,
            IEIFLAG_ASPECT = 4,
            IEIFLAG_OFFLINE = 8,
            IEIFLAG_GLEAM = 16,
            IEIFLAG_SCREEN = 32,
            IEIFLAG_ORIGSIZE = 64,
            IEIFLAG_NOSTAMP = 128,
            IEIFLAG_NOBORDER = 256,
            IEIFLAG_QUALITY = 512
        }

        [Flags]
        private enum ESFGAO
        {
            SFGAO_CANCOPY = 1,
            SFGAO_CANMOVE = 2,
            SFGAO_CANLINK = 4,
            SFGAO_CANRENAME = 16,
            SFGAO_CANDELETE = 32,
            SFGAO_HASPROPSHEET = 64,
            SFGAO_DROPTARGET = 256,
            SFGAO_CAPABILITYMASK = 375,
            SFGAO_LINK = 65536,
            SFGAO_SHARE = 131072,
            SFGAO_READONLY = 262144,
            SFGAO_GHOSTED = 524288,
            SFGAO_DISPLAYATTRMASK = 983040,
            SFGAO_FILESYSANCESTOR = 268435456,
            SFGAO_FOLDER = 536870912,
            SFGAO_FILESYSTEM = 1073741824,
            SFGAO_HASSUBFOLDER = -2147483648,
            SFGAO_CONTENTSMASK = -2147483648,
            SFGAO_VALIDATE = 16777216,
            SFGAO_REMOVABLE = 33554432,
            SFGAO_COMPRESSED = 67108864,
        }

        [Flags]
        private enum ESHCONTF
        {
            SHCONTF_FOLDERS = 32,
            SHCONTF_NONFOLDERS = 64,
            SHCONTF_INCLUDEHIDDEN = 128,
        }

        [Flags]
        private enum ESHGDN
        {
            SHGDN_NORMAL = 0,
            SHGDN_INFOLDER = 1,
            SHGDN_FORADDRESSBAR = 16384,
            SHGDN_FORPARSING = 32768
        }

        #endregion Private Enums

        #region Internal Interfaces

        [ComImport, Guid("000214F2-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        internal interface IEnumIDList
        {
            [PreserveSig]
            [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
            HResult Next(uint celt, out IntPtr rgelt, out uint pceltFetched);

            [PreserveSig]
            [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
            HResult Skip([In] uint celt);

            [PreserveSig]
            [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
            HResult Reset();

            [PreserveSig]
            [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
            HResult Clone([MarshalAs(UnmanagedType.Interface)] out IEnumIDList ppenum);
        }

        #endregion Internal Interfaces

        #region Private Interfaces

        [ComImport(), Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IExtractImage
        {
            [PreserveSig()]
            HResult GetLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszPathBuffer, int cch, ref int pdwPriority, ref SIZE prgSize, int dwRecClrDepth, ref int pdwFlags);

            [PreserveSig()]
            HResult Extract(ref IntPtr phBmpThumbnail);
        }

        [ComImport()]
        [Guid("000214E6-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        private interface IShellFolder
        {
            /// <summary>
            /// Translates a file object's or folder's display name into an item identifier items.
            /// </summary>
            /// <returns>error code, if any</returns>
            [PreserveSig()]
            HResult ParseDisplayName(
                // Optional window handle
                IntPtr hwnd,
                // Optional bind context that controls the
                // parsing operation. This parameter is
                // normally set to NULL.
                IntPtr pbc,
                // Null-terminated UNICODE string with the display name.
                [In(), MarshalAs(UnmanagedType.LPWStr)]
                string pszDisplayName,
                // Pointer to a ULONG value that receives the
                // number of characters of the
                // display name that was parsed.
                out uint pchEaten,
                // Pointer to an ITEMIDLIST pointer that receives
                // the item identifier items for
                // the object.
                out IntPtr ppidl,
                // Optional parameter that can be used to
                // query for file attributes.
                // this can be values from the SFGAO enum
                ref uint pdwAttributes);

            [PreserveSig()]
            HResult EnumObjects(IntPtr hwndOwner, [MarshalAs(UnmanagedType.U4)]ESHCONTF grfFlags, ref IEnumIDList ppenumIDList);

            /// <summary>
            /// Retrieves an IShellFolder object for a subfolder.
            /// </summary>
            /// <returns>error code, if any</returns>
            [PreserveSig()]
            HResult BindToObject(
                // Address of an ITEMIDLIST structure (PIDL)
                // that identifies the subfolder.
                IntPtr pidl,
                // Optional address of an IBindCtx interface on
                // a bind context object to be
                // used during this operation.
                IntPtr pbc,
                // Identifier of the interface to return.
                [In()] ref Guid riid,
                // Address that receives the interface pointer.
                //[MarshalAs(UnmanagedType.Interface)]
                out IntPtr ppv);

            void BindToStorage(IntPtr pidl, IntPtr pbcReserved, ref Guid riid, IntPtr ppvObj);

            [PreserveSig()]
            HResult CompareIDs(IntPtr lParam, IntPtr pidl1, IntPtr pidl2);

            [PreserveSig()]
            HResult CreateViewObject(IntPtr hwndOwner, ref Guid riid, IntPtr ppvOut);

            [PreserveSig()]
            HResult GetAttributesOf(int cidl, IntPtr apidl, [MarshalAs(UnmanagedType.U4)]ref ESFGAO rgfInOut);

            [PreserveSig()]
            HResult GetUIObjectOf(IntPtr hwndOwner, int cidl, ref IntPtr apidl, ref Guid riid, ref int prgfInOut, ref IntPtr ppvOut);

            [PreserveSig()]
            HResult GetDisplayNameOf(IntPtr pidl, [MarshalAs(UnmanagedType.U4)]ESHGDN uFlags, out IntPtr lpName);

            [PreserveSig()]
            HResult SetNameOf(IntPtr hwndOwner, IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)]string lpszName, [MarshalAs(UnmanagedType.U4)] ESHCONTF uFlags, ref IntPtr ppidlOut);
        }

        #endregion Private Interfaces

        #region Public Methods

        public static Bitmap ExtractThumbnail(string path)
        {
            if (path == null)
                throw new ArgumentNullException(nameof(path));

            if (File.Exists(path) || Directory.Exists(path))
            {
                IntPtr pidlMain = IntPtr.Zero;
                IntPtr pidlChild = IntPtr.Zero;
                if (NativeMethods.SHGetDesktopFolder(out IShellFolder desktopFolder) == HResult.S_OK)
                {
                    IShellFolder parentFolder = null;
                    try
                    {
                        uint pdwAttrib = 0;
                        string directroryPath = Path.GetDirectoryName(path);
                        string basePath = Path.GetFileName(path);

                        HResult hRes = desktopFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, directroryPath, out uint cParsed, out pidlMain, ref pdwAttrib);

                        if (hRes == HResult.S_OK)
                        {
                            IntPtr ppv = IntPtr.Zero;
                            hRes = desktopFolder.BindToObject(pidlMain, IntPtr.Zero, ref IID_ShellFolder, out ppv);
                            if (hRes == HResult.S_OK)
                            {
                                parentFolder = (IShellFolder)Marshal.GetTypedObjectForIUnknown(ppv, typeof(IShellFolder));

                                if (parentFolder != null)
                                {
                                    uint childAttrib = 0;

                                    hRes = parentFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, basePath, out uint parsedChild, out pidlChild, ref childAttrib);

                                    if (hRes == HResult.S_OK)
                                    {
                                        int prgf = 0;
                                        IntPtr ppvOut = IntPtr.Zero;

                                        hRes = parentFolder.GetUIObjectOf(IntPtr.Zero, 1, ref pidlChild, ref IID_IExtractImage, ref prgf, ref ppvOut);

                                        if (hRes == HResult.S_OK)
                                        {
                                            IExtractImage extractImage = (IExtractImage)Marshal.GetTypedObjectForIUnknown(ppvOut, typeof(IExtractImage));

                                            if (extractImage != null)
                                            {
                                                IntPtr hbitmap = IntPtr.Zero;
                                                try
                                                {
                                                    SIZE sz = new SIZE { cx = 256, cy = 256 };

                                                    StringBuilder location = new StringBuilder(260);
                                                    int priority = 0;
                                                    EIEIFLAG flags = EIEIFLAG.IEIFLAG_ORIGSIZE | EIEIFLAG.IEIFLAG_QUALITY;
                                                    int uFlags = (int)flags;

                                                    hRes = extractImage.GetLocation(location, location.Capacity, ref priority, ref sz, 32, ref uFlags);

                                                    if (hRes == HResult.S_OK)
                                                    {
                                                        hRes = extractImage.Extract(ref hbitmap);

                                                        if (hRes == HResult.S_OK && hbitmap != IntPtr.Zero)
                                                            return Romy.Core.Shell.Common.FixShellBitmap(path, Image.FromHbitmap(hbitmap));
                                                    }
                                                }
                                                finally
                                                {
                                                    Marshal.ReleaseComObject(extractImage);

                                                    if (hbitmap != IntPtr.Zero)
                                                        NativeMethods.DeleteObject(hbitmap);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                    finally
                    {
                        if (pidlChild != IntPtr.Zero)
                            Marshal.FreeCoTaskMem(pidlChild);

                        if (pidlMain != IntPtr.Zero)
                            Marshal.FreeCoTaskMem(pidlMain);

                        if (parentFolder != null)
                            Marshal.ReleaseComObject(parentFolder);

                        Marshal.ReleaseComObject(desktopFolder);
                    }
                }
            }
            return null;
        }

        #endregion Public Methods

        #region Private Structs

        [StructLayout(LayoutKind.Sequential)]
        private struct SIZE
        {
            public int cx;

            public int cy;
        }

        #endregion Private Structs

        #region Private Classes

        [SuppressUnmanagedCodeSecurity]
        private static class NativeMethods
        {
            #region Internal Methods

            [DllImport("gdi32", CharSet = CharSet.Auto)]
            internal extern static int DeleteObject(IntPtr hObject);

            [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            internal extern static HResult SHGetDesktopFolder([MarshalAs(UnmanagedType.Interface)] out IShellFolder ppshf);

            #endregion Internal Methods
        }

        #endregion Private Classes
    }
}

Both of those call this method to fix directory thumbnails:

using System;
using System.Drawing;
using System.IO;

namespace Romy.Core.Shell
{
    internal static class Common
    {
        public static Bitmap FixShellBitmap(string filename, Bitmap result)
        {
            // Some special handling for certain thumbnail types.
            try
            {
                if (Directory.Exists(filename))
                {
                    result = result.FloodFill(Point.Empty, Color.White, Color.FromArgb(1, 20, 20)) as Bitmap;
                    result.MakeTransparent();
                }

                if (result != null && (result.Size.Width > 256 || result.Size.Height > 256))
                    result = result.Resize(256, 256) as Bitmap;
            }
            catch (Exception ex)
            {
                ex.Log();
                if (result != null)
                {
                    result.Dispose();
                    result = null;
                }
            }
            return result;
        }
    }
}

And that helper method calls my image Resize method that I shared recently, as well as the following code that uses AForge.NET to flood fill the directory thumbnails:

using AForge.Imaging;
using AForge.Imaging.Filters;
using System.Drawing.Imaging;

namespace System.Drawing
{
    /// <summary>Flood-fill wrapper that currently wraps the AForge.Net <b>PointedColorFloodFill</b>.</summary>
    /// <remarks>This was added to the solution because of an issue with directory thumbnails. The system
    /// returns them with a white region around the outside, where it should be transparent.</remarks>
    public static class FloodFiller
    {
        /// <summary>A Flood-Fill extension method on <see cref="Image"/>.</summary>
        /// <remarks>
        /// This overload uses a default location and fills the target <b>Image</b> from the outside. To
        /// specify a location, use one of the overloads that accepts a <see cref="Point"/>
        /// parameter.</remarks>
        /// <param name="source">The source image to flood-fill.</param>
        /// <param name="fillColor">The colour with which to flood-fill.</param>
        /// <returns>A new image, that is a flood-filled copy of the source image.</returns>
        public static Image FloodFill(this Image source, Color fillColor) => source.FloodFill(Point.Empty, fillColor, Color.FromArgb(20, 20, 20));

        /// <summary>A Flood-Fill extension method on <see cref="Image"/>.</summary>
        /// <param name="source">The source image to flood-fill.</param>
        /// <param name="location">The location in source at which to start the flood-fill.</param>
        /// <param name="fillColor">The colour with which to flood-fill.</param>
        /// <returns>A new image, that is a flood-filled copy of the source image.</returns>
        public static Image FloodFill(this Image source, Point location, Color fillColor) => FloodFill(source, location, fillColor, Color.FromArgb(20, 20, 20));

        /// <summary>A Flood-Fill extension method on <see cref="Image"/>.</summary>
        /// <param name="source">The source image to flood-fill.</param>
        /// <param name="location">The location in source at which to start the flood-fill.</param>
        /// <param name="fillColor">The colour with which to flood-fill.</param>
        /// <param name="tolerance">A tolerance which determines the sensitivity of the flood-fill operation.</param>
        /// <returns>A new image, that is a flood-filled copy of the source image.</returns>
        public static Image FloodFill(this Image source, Point location, Color fillColor, Color tolerance)
        {
            /* Combining the flood-fill with the Shrink filter works better than a flood-fill alone
             * in this application, since we use 256x256 thumbnails - shrinking the outer colour away
             * before scaling the image down may produce a slightly larger image in the thumb bounds. */
            UnmanagedImage unmanagedImage = UnmanagedImage.FromManagedImage(
                source.PixelFormat == PixelFormat.Format24bppRgb ? source as Bitmap : source.CopyImage() as Bitmap); // Shrink requires 24 bit.

            FiltersSequence sequence = new FiltersSequence(
                new PointedColorFloodFill
                {
                    FillColor = fillColor,
                    StartingPoint = new AForge.IntPoint(location.X, location.Y),
                    Tolerance = tolerance
                },
                new Shrink(fillColor));

            return sequence.Apply(unmanagedImage).ToManagedImage();
        }

        /// <summary>Trims pixels of the specified colour from the outside of the
        /// source image, and returns a transparent copy of the image.</summary>
        /// <remarks>Flood-fills and calls MakeTransparent.</remarks>
        public static Image Trim(this Image source, Color colorToRemove)
        {
            Bitmap trimmed = null;
            try
            {
                trimmed = source.FloodFill(Point.Empty, colorToRemove, Color.FromArgb(20, 20, 20)) as Bitmap;
                trimmed.MakeTransparent();
            }
            catch
            {
                if (trimmed != null)
                    trimmed.Dispose();

                throw;
            }
            return trimmed;
        }

        public static Image Trim(this Image source, Color colorToRemove, Color tolerance)
        {
            Bitmap trimmed = null;
            try
            {
                trimmed = source.FloodFill(Point.Empty, colorToRemove, tolerance) as Bitmap;
                trimmed.MakeTransparent();
            }
            catch
            {
                if (trimmed != null)
                    trimmed.Dispose();

                throw;
            }
            return trimmed;
        }
    }
}

The above code also uses my Image.CopyImage extension method. I shared that recently here, but here it is again:

/// <summary>Creates a 24 bit-per-pixel copy of the source image.</summary>
public static Image CopyImage(this Image image) => CopyImage(image, PixelFormat.Format24bppRgb);

/// <summary>Creates a copy of the source image with the specified pixel format.</summary><remarks>
/// This can also be achieved with the <see cref="Bitmap.Clone(int, int, PixelFormat)"/>
/// overload, but I have had issues with that method.</remarks>
public static Image CopyImage(this Image image, PixelFormat format)
{
    if (image == null)
        throw new ArgumentNullException(nameof(image));

    // Don't try to draw a new Bitmap with an indexed pixel format.
    if (format == PixelFormat.Format1bppIndexed || format == PixelFormat.Format4bppIndexed || format == PixelFormat.Format8bppIndexed || format == PixelFormat.Indexed)
        return (image as Bitmap).Clone(new Rectangle(0, 0, image.Width, image.Height), format);

    Image result = null;
    try
    {
        result = new Bitmap(image.Width, image.Height, format);

        using (Graphics graphics = Graphics.FromImage(result))
        {
            graphics.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic;
            graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias;
            graphics.CompositingQuality = Drawing2D.CompositingQuality.HighQuality;

            graphics.DrawImage(image, 0, 0, result.Width, result.Height);
        }
    }
    catch
    {
        if (result != null)
            result.Dispose();

        throw;
    }
    return result;
}
Posted in Programming | Tagged , , , , | Leave a comment

Some Task extensions

I’m on a 30 day Facebook ban because I called some Donald Trump supporters white trash. Apparently I, a white South African, am guilty of hate speech and racism against white people. But actual hate speech, often subtle, always insidious, goes unchecked. (Actual hate speech which consists, for example, of rhetoric where white people argue about the very definition of concentration camps.) Sorry, I try to avoid getting at all political here (unlike my other blog)… but I thought it might be worth mentioning. I’m active here again lately because I’m not spending time sharing memes and political stuff on Facebook.

I’ve collected some Task extension methods over a couple of years… These are not mine, but were written by various others, including Jon Skeet, Stephen Toub, and others. Of these, I use TimeoutAfter and ForEachAsync extensively.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Romy.Core
{
    internal struct VoidTypeStruct { }

    public static class TaskExtensions
    {
        /* This came out of the EduAsync series of blog posts by Jon Skeet. */

        /// <summary>Returns a sequence of tasks which will be observed to complete with the same set of
        /// results as the given input tasks, but in the order in which the original tasks complete.</summary>
        public static IEnumerable<Task<T>> OrderByCompletion<T>(this IEnumerable<Task<T>> inputTasks)
        {
            // Copy the input so we know it'll be stable, and we don't evaluate it twice
            List<Task<T>> inputTaskList = inputTasks.ToList();

            // Could use Enumerable.Range here, if we wanted...
            List<TaskCompletionSource<T>> completionSourceList = new List<TaskCompletionSource<T>>(inputTaskList.Count);
            for (int i = 0; i < inputTaskList.Count; i++)
            {
                completionSourceList.Add(new TaskCompletionSource<T>());
            }

            // At any one time, this is "the index of the box we've just filled".
            // It would be nice to make it nextIndex and start with 0, but Interlocked.Increment
            // returns the incremented value...
            int prevIndex = -1;

            // We don't have to create this outside the loop, but it makes it clearer
            // that the continuation is the same for all tasks.
            void continuation(Task<T> completedTask)
            {
                int index = Interlocked.Increment(ref prevIndex);
                TaskCompletionSource<T> source = completionSourceList[index];
                PropagateResult(completedTask, source);
            }

            foreach (Task<T> inputTask in inputTaskList)
            {
                // TODO: Work out whether TaskScheduler.Default is really the right one to use.
                inputTask.ContinueWith(continuation,
                                       CancellationToken.None,
                                       TaskContinuationOptions.ExecuteSynchronously,
                                       TaskScheduler.Default);
            }

            return completionSourceList.Select(source => source.Task);
        }

        /// <summary>Propagates the status of the given task (which must be
        /// completed) to a task completion source (which should not be).</summary>
        private static void PropagateResult<T>(Task<T> completedTask,
            TaskCompletionSource<T> completionSource)
        {
            switch (completedTask.Status)
            {
                case TaskStatus.Canceled:
                    completionSource.TrySetCanceled();
                    break;
                case TaskStatus.Faulted:
                    completionSource.TrySetException(completedTask.Exception.InnerExceptions);
                    break;
                case TaskStatus.RanToCompletion:
                    completionSource.TrySetResult(completedTask.Result);
                    break;
                default:
                    // TODO: Work out whether this is really appropriate. Could set
                    // an exception in the completion source, of course...
                    completionSource.TrySetException(new ArgumentException("Task was not completed"));
                    break;
            }
        }

        /* These are from an article by Stephen Toub. */
        public static async Task Then(this Task antecedent, Action continuation)
        {
            await antecedent;
            continuation();
        }

        public static async Task<TNewResult> Then<TNewResult>(this Task antecedent, Func<TNewResult> continuation)
        {
            await antecedent;
            return continuation();
        }

        public static async Task Then<TResult>(this Task<TResult> antecedent, Action<TResult> continuation) => continuation(await antecedent);

        public static async Task<TNewResult> Then<TResult, TNewResult>(this Task<TResult> antecedent, Func<TResult, TNewResult> continuation) => continuation(await antecedent);

        public static async Task Then(this Task task, Func<Task> continuation)
        {
            await task;
            await continuation();
        }

        public static async Task<TNewResult> Then<TNewResult>(this Task task, Func<Task<TNewResult>> continuation)
        {
            await task;
            return await continuation();
        }

        public static async Task Then<TResult>(this Task<TResult> task, Func<TResult, Task> continuation) => await continuation(await task);

        public static async Task<TNewResult> Then<TResult, TNewResult>(this Task<TResult> task, Func<TResult, Task<TNewResult>> continuation) => await continuation(await task);

        /* Task.TimeoutAfter extension methods: Based on sample code by Joe Hoag of Microsoft, from the article at
         * http://blogs.msdn.com/b/pfxteam/archive/2011/11/10/10235834.aspx, Crafting a Task.TimeoutAfter Method.
         * I did not change the original code; only added the overloads that take a TimeSpan argument.
         *
         * Note that the returned Task on a timeout is set to the faulted state with a TimeoutException, so when
         * calling this you need to catch the TimeoutException, when it times out. */
        public static Task TimeoutAfter(this Task task, TimeSpan timeoutSpan) => task.TimeoutAfter((int)timeoutSpan.TotalMilliseconds);

        public static Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeoutSpan) => task.TimeoutAfter((int)timeoutSpan.TotalMilliseconds) as Task<TResult>;

        public static Task TimeoutAfter(this Task task, int millisecondsTimeout)
        {
            // Short-circuit #1: infinite timeout or task already completed
            if (task.IsCompleted || (millisecondsTimeout == Timeout.Infinite))
            {
                // Either the task has already completed or timeout will never occur.
                // No proxy necessary.
                return task;
            }

            // tcs.Task will be returned as a proxy to the caller
            TaskCompletionSource<VoidTypeStruct> tcs = new TaskCompletionSource<VoidTypeStruct>();

            // Short-circuit #2: zero timeout
            if (millisecondsTimeout == 0)
            {
                // We've already timed out.
                tcs.SetException(new TimeoutException());
                return tcs.Task;
            }

            // Set up a timer to complete after the specified timeout period
            Timer timer = new Timer(state =>
            {
                // Recover your state information
                // Fault our proxy with a TimeoutException
                ((TaskCompletionSource<VoidTypeStruct>)state).TrySetException(new TimeoutException());
            }, tcs, millisecondsTimeout, Timeout.Infinite);

            // Wire up the logic for what happens when source task completes
            task.ContinueWith((antecedent, state) =>
            {
                // Recover our state data
                Tuple<Timer, TaskCompletionSource<VoidTypeStruct>> tuple = (Tuple<Timer, TaskCompletionSource<VoidTypeStruct>>)state;

                // Cancel the Timer
                tuple.Item1.Dispose();

                // Marshal results to proxy
                MarshalTaskResults(antecedent, tuple.Item2);
            }, Tuple.Create(timer, tcs), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

            return tcs.Task;
        }

        public static Task TimeoutAfter<TResult>(this Task<TResult> task, int millisecondsTimeout)
        {
            // Short-circuit #1: infinite timeout or task already completed
            if (task.IsCompleted || (millisecondsTimeout == Timeout.Infinite))
            {
                // Either the task has already completed or timeout will never occur.
                // No proxy necessary.
                return task;
            }

            // tcs.Task will be returned as a proxy to the caller
            TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();

            // Short-circuit #2: zero timeout
            if (millisecondsTimeout == 0)
            {
                // We've already timed out.
                tcs.SetException(new TimeoutException());
                return tcs.Task;
            }

            // Set up a timer to complete after the specified timeout period
            Timer timer = new Timer(state =>
            {
                // Recover your state information
                // Fault our proxy with a TimeoutException
                ((TaskCompletionSource<TResult>)state).TrySetException(new TimeoutException());
            }, tcs, millisecondsTimeout, Timeout.Infinite);

            // Wire up the logic for what happens when source task completes
            task.ContinueWith((antecedent, state) =>
            {
                // Recover our state data
                Tuple<Timer, TaskCompletionSource<TResult>> tuple = (Tuple<Timer, TaskCompletionSource<TResult>>)state;

                // Cancel the Timer
                tuple.Item1.Dispose();

                // Marshal results to proxy
                MarshalTaskResults(antecedent, tuple.Item2);
            }, Tuple.Create(timer, tcs), CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);

            return tcs.Task;
        }

        internal static void MarshalTaskResults<TResult>(Task source, TaskCompletionSource<TResult> proxy)
        {
            switch (source.Status)
            {
                case TaskStatus.Faulted:
                    proxy.TrySetException(source.Exception);
                    break;
                case TaskStatus.Canceled:
                    proxy.TrySetCanceled();
                    break;
                case TaskStatus.RanToCompletion:
                    proxy.TrySetResult(
                        !(source is Task<TResult> castedSource) ? default : // source is a Task
                            castedSource.Result);                 // source is a Task<TResult>
                    break;
            }
        }

        public static Task YieldTo(this Task task, TaskScheduler scheduler) => task.ContinueWith(antecedent =>
                                                                                         {
                                                                                             Task.Yield();
                                                                                             return antecedent.ConfigureAwait(false);
                                                                                         }, scheduler);

        /* Technically, these two should be declared in CollectionExtensions, but this method is used by Zipper.exe.
         * Zipper can add a reference to this file, but not to CollectionExtensions.cs, which references System.Windows.Forms */

        /// <summary>A ForEachAsync implementation. Based on a sample in an article by Stephen Toub,
        /// <a href="http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx">
        /// Implementing a simple ForEachAsync, part 2</a>.</summary>
        /// <remarks>
        /// I've changed this from calling <b>Task.Run</b> to call <b>Task.Factory.StartNew</b> in order to have it use my
        /// custom ParallelTaskScheduler rather than the default. (This was intended to be an experimental change, but I
        /// decided to leave it like this.)</remarks>
        public static Task ForEachAsync<T>(this IEnumerable<T> source,
            int maxDegreeOfParallelism,
            Func<T, Task> body) => Task.WhenAll(
                from partition in Partitioner.Create(source).GetPartitions(maxDegreeOfParallelism)
                select Tasks.Run(async () =>
                {
                    using (partition)
                        while (partition.MoveNext())
                            await body(partition.Current);
                }));

        /// <summary>An asynchronous ForAsync implementation.</summary>
        /// <remarks>It simply creates an <b>Enumerable.Range</b> and wraps <b>ForEachAsync</b>.</remarks>
        public static Task ForAsync(int fromInclusive, int toExclusive, int maxDegreeOfParallelism, Func<int, Task> body) => Enumerable.Range(
                fromInclusive, toExclusive).ForEachAsync(maxDegreeOfParallelism, async i => await body(i));
    }
}
Posted in Programming | Tagged , | Leave a comment

Here are my Stream extensions, including a c# wrapper for the ReOpenFile Windows API function, and some code that uses my CopyToStreamAsync extension method.

I don’t have many Stream extension methods, in fact there are only two, but I think they are useful.

  1. CopyToStreamAsync, an extension to Stream, is an alternative to Stream.CopyToAsync, and is featured in one of my most popular posts.
  2. ReOpen, an extension to FileStream, allows reopening a file a second time, and is a .NET wrapper around the ReOpenFile Windows API function.

I’ve shared the first extension above, but never shared the whole file.

I wrote the ReOpen method back in 2013 after reading a blog post by Raymond Chen explaining why you might want to use that function. Long story short – say you open a file synchronously, and want to change the flags and open it again asynchronously. Anyway, my implementation calls the native API and returns a .NET FileStream via a SafeHandle around the native file handle.

So here’s the two Stream extensions… Enjoy.

using Microsoft.Win32.SafeHandles;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace Romy.Core
{
    public static class StreamExtensions
    {
        #region Private Fields

        private const uint FILE_FLAG_OVERLAPPED = 0x40000000;

        #endregion Private Fields

        #region Private Enums

        [Flags]
        private enum EFileAccess : uint
        {
            // Standard Section
            AccessSystemSecurity = 0x1000000,   // AccessSystemAcl access type

            MaximumAllowed = 0x2000000,     // MaximumAllowed access type

            Delete = 0x10000,
            ReadControl = 0x20000,
            WriteDAC = 0x40000,
            WriteOwner = 0x80000,
            Synchronize = 0x100000,

            StandardRightsRequired = 0xF0000,
            StandardRightsRead = ReadControl,
            StandardRightsWrite = ReadControl,
            StandardRightsExecute = ReadControl,
            StandardRightsAll = 0x1F0000,
            SpecificRightsAll = 0xFFFF,

            FILE_READ_DATA = 0x0001,        // file & pipe
            FILE_LIST_DIRECTORY = 0x0001,       // directory
            FILE_WRITE_DATA = 0x0002,       // file & pipe
            FILE_ADD_FILE = 0x0002,         // directory
            FILE_APPEND_DATA = 0x0004,      // file
            FILE_ADD_SUBDIRECTORY = 0x0004,     // directory
            FILE_CREATE_PIPE_INSTANCE = 0x0004, // named pipe
            FILE_READ_EA = 0x0008,          // file & directory
            FILE_WRITE_EA = 0x0010,         // file & directory
            FILE_EXECUTE = 0x0020,          // file
            FILE_TRAVERSE = 0x0020,         // directory
            FILE_DELETE_CHILD = 0x0040,     // directory
            FILE_READ_ATTRIBUTES = 0x0080,      // all
            FILE_WRITE_ATTRIBUTES = 0x0100,     // all

            // Generic Section
            GenericRead = 0x80000000,

            GenericWrite = 0x40000000,
            GenericExecute = 0x20000000,
            GenericAll = 0x10000000,

            SPECIFIC_RIGHTS_ALL = 0x00FFFF,

            FILE_ALL_ACCESS =
            StandardRightsRequired |
            Synchronize |
            0x1FF,

            FILE_GENERIC_READ =
            StandardRightsRead |
            FILE_READ_DATA |
            FILE_READ_ATTRIBUTES |
            FILE_READ_EA |
            Synchronize,

            FILE_GENERIC_WRITE =
            StandardRightsWrite |
            FILE_WRITE_DATA |
            FILE_WRITE_ATTRIBUTES |
            FILE_WRITE_EA |
            FILE_APPEND_DATA |
            Synchronize,

            FILE_GENERIC_EXECUTE =
            StandardRightsExecute |
              FILE_READ_ATTRIBUTES |
              FILE_EXECUTE |
              Synchronize
        }

        [Flags]
        private enum EFileShare : uint
        {
            None = 0x00000000,

            /// <summary>
            /// Enables subsequent open operations on an object to request read access.
            /// Otherwise, other processes cannot open the object if they request read access.
            /// If this flag is not specified, but the object has been opened for read access, the function fails.
            /// </summary>
            Read = 0x00000001,

            /// <summary>
            /// Enables subsequent open operations on an object to request write access.
            /// Otherwise, other processes cannot open the object if they request write access.
            /// If this flag is not specified, but the object has been opened for write access, the function fails.
            /// </summary>
            Write = 0x00000002,

            /// <summary>
            /// Enables subsequent open operations on an object to request delete access.
            /// Otherwise, other processes cannot open the object if they request delete access.
            /// If this flag is not specified, but the object has been opened for delete access, the function fails.
            /// </summary>
            Delete = 0x00000004
        }

        #endregion Private Enums

        #region Public Methods

        public static Task CopyToStreamAsync(this Stream source, Stream destination) => source.CopyToStreamAsync(destination, Constants.BufferSize);

        /// <summary>An implementation to copy asynchronously from one stream to another,
        /// similar to <see cref="Stream.CopyToAsync(Stream)"/></summary>
        /// <remarks>
        /// This was written because the default implementation would sometimes throw OutOfMemoryExceptions
        /// in my FileAsync.ReadAllBytesAsync method, when opening large Bitmap files. Reading with a fixed
        /// size small buffer works well as a general solution, though I have also begun using a larger
        /// buffer to improve performance with larger files.</remarks>
        public static async Task CopyToStreamAsync(this Stream source, Stream destination, int bufferSize)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));

            if (destination == null)
                throw new ArgumentNullException(nameof(destination));

            if (bufferSize <= 0)
                throw new ArgumentOutOfRangeException(nameof(bufferSize), $"{nameof(bufferSize)} must be greater than zero");

            /* The source stream may not support seeking; e.g. a stream
             * returned by ZipArchiveEntry.Open() or a network stream. */
            int size = bufferSize;
            bool canSeek = source.CanSeek;

            if (canSeek)
            {
                try
                {
                    size = (int)Math.Min(bufferSize, source.Length);
                }
                catch (NotSupportedException) { canSeek = false; }
            }

            byte[] buffer = new byte[size];
            long remaining = canSeek ? source.Length : 0;

            /* If the stream is seekable, seek through it until all bytes are read.
             * If we read less than the expected number of bytes, it indicates an
             * error, so throw the appropriate exception.
             *
             * If the stream is not seekable, loop until we read 0 bytes. (It's not
             * an error in this case.) */
            while (!canSeek || remaining > 0)
            {
                int read = await source.ReadAsync(buffer, 0, size);

                if (read <= 0)
                {
                    if (canSeek)
                        throw new EndOfStreamException(
                            string.Format("End of stream reached, but {0} remained to be read.",
                            FormatBytes(remaining)));
                    else
                        break;
                }

                await destination.WriteAsync(buffer, 0, read);
                remaining -= canSeek ? read : 0;
            }
        }

        public static FileStream Reopen(this FileStream source, FileAccess access, FileShare share, int bufferSize, bool useAsync)
        {
            EFileAccess fileAccess = EFileAccess.GenericWrite;

            switch (access)
            {
                case FileAccess.Read:
                    fileAccess = EFileAccess.GenericRead;
                    break;
                case FileAccess.ReadWrite:
                    fileAccess = EFileAccess.GenericRead | EFileAccess.GenericWrite;
                    break;
                case FileAccess.Write:
                    fileAccess = EFileAccess.GenericWrite;
                    break;
            }

            EFileShare fileShare = EFileShare.Read | EFileShare.Write;

            switch (share)
            {
                case FileShare.Delete:
                    fileShare = EFileShare.Delete;
                    break;
                case FileShare.Inheritable:
                    fileShare = source.CanRead ? EFileShare.Read : source.CanWrite ? EFileShare.Write : EFileShare.None;
                    break;
                case FileShare.None:
                    fileShare = EFileShare.None;
                    break;
                case FileShare.Read:
                    fileShare = EFileShare.Read;
                    break;
                case FileShare.ReadWrite:
                    fileShare = EFileShare.Read | EFileShare.Write;
                    break;
                case FileShare.Write:
                    fileShare = EFileShare.Write;
                    break;
            }

            return new FileStream(NativeMethods.ReOpenFile(source.SafeFileHandle, (uint)fileAccess, (uint)fileShare, useAsync ? FILE_FLAG_OVERLAPPED : 0), access, bufferSize, useAsync);
        }

        #endregion Public Methods

        #region Private Methods

        /// <summary>Redefined here because this is also used in the Zipper project,
        /// which can't use FileUtils, or any other code that calls ex.Log().</summary>
        private static string FormatBytes(long bytes)
        {
            const long KiloByte = 1024L;
            const long MegaByte = KiloByte * KiloByte;
            const long GigaByte = MegaByte * KiloByte;
            const long TeraByte = GigaByte * KiloByte;
            const long PetaByte = TeraByte * KiloByte;
            const long ExaByte = PetaByte * KiloByte;

            string formattedBytes = string.Empty;

            if (bytes < KiloByte)
                formattedBytes = string.Format("{0:F2} bytes", bytes);
            else if (bytes >= KiloByte && bytes < MegaByte)
                formattedBytes = string.Format("{0:F2} KB", Math.Round((double)bytes / KiloByte, 2, MidpointRounding.AwayFromZero));
            else if (bytes >= MegaByte && bytes < GigaByte)
                formattedBytes = string.Format("{0:F2} MB", Math.Round((double)bytes / MegaByte, 2, MidpointRounding.AwayFromZero));
            else if (bytes >= GigaByte && bytes < TeraByte)
                formattedBytes = string.Format("{0:F2} GB", Math.Round((double)bytes / GigaByte, 2, MidpointRounding.AwayFromZero));
            else if (bytes >= TeraByte && bytes < PetaByte)
                formattedBytes = string.Format("{0:F2} TB", Math.Round((double)bytes / TeraByte, 2, MidpointRounding.AwayFromZero));
            else if (bytes >= PetaByte && bytes < ExaByte)
                formattedBytes = string.Format("{0:F2} PB", Math.Round((double)bytes / PetaByte, 2, MidpointRounding.AwayFromZero));
            else if (bytes >= ExaByte)
                formattedBytes = string.Format("{0:F2} EB", Math.Round((double)bytes / ExaByte, 2, MidpointRounding.AwayFromZero));

            return formattedBytes;
        }

        #endregion Private Methods

        #region Private Classes

        private static class NativeMethods
        {
            #region Public Methods

            [DllImport("kernel32", SetLastError = true)]
            public static extern SafeFileHandle ReOpenFile(SafeFileHandle hOriginalFile, uint dwAccess, uint dwShareMode, uint dwFlags);

            #endregion Public Methods
        }

        #endregion Private Classes
    }
}

A practical example of using CopyToStreamAsync follows… These are async equivalents of methods like File.ReadAllBytes. Actually that was why I wrote CopyToStreamAsync. I found that Stream.CopyToAsync would throw OutOfMemoryExceptions too often in my FileAsync.ReadAllBytesAsync when reading large bitmaps. Mine does not suffer from that problem, although I don’t really know why.

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Romy.Core
{
    /// <summary>These are not extension methods, but are static methods
    /// similar to certain synchronous static methods on System.IO.File.
    /// (File.ReadAllBytes; File.WriteAllBytes; File.ReadAllLines etc.)</summary>
    /// <remarks>All these methods rely on my other extension methods,
    /// especially <b>StreamExtensions.CopyToStreamAsync</b>.</remarks>
    public static class FileAsync
    {
        #region Public Fields

        public const int BufferSize = 0x2000;

        public const int LargeBufferSize = BufferSize * 1024;

        #endregion Public Fields

        #region Private Fields

        private static readonly byte[] NoBytes = Enumerable.Empty<byte>().ToArray();

        #endregion Private Fields

        #region Public Methods

        public static Task AppendAllBytesAsync(string path, byte[] bytes) => AppendAllBytesAsync(path, bytes, BufferSize);

        public static async Task AppendAllBytesAsync(string path, byte[] bytes, int bufferSize)
        {
            using (MemoryStream memoryStream = new MemoryStream(bytes))
            {
                using (FileStream stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.None, bufferSize, true))
                {
                    await memoryStream.CopyToStreamAsync(stream, bufferSize);
                }
            }
        }

        /// <summary>Asynchronously appends lines to a file, and then closes the file.</summary>
        /// <param name="path">The file to append the lines to. The file is created if it does not already exist.</param>
        /// <param name="contents">The lines to append to the file.</param>
        /// <returns>A Task that represents completion of the method.</returns>
        public static async Task AppendAllLinesAsync(string path, IEnumerable<string> contents)
        {
            List<string> lines = File.Exists(path) ? new List<string>(await ReadAllLinesAsync(path)) : new List<string>();
            lines.AddRange(contents);

            await WriteAllLinesAsync(path, lines.ToArray());
        }

        /// <summary>Asynchronously opens a binary file, reads the contents of
        /// the file into a byte array, and then closes the file.</summary>
        /// <param name="filename">The file to open for reading.</param>
        /// <returns>A Task that will, on completion of the method,
        /// contain a byte array of the contents of the file.</returns>
        public static Task<byte[]> ReadAllBytesAsync(string filename) => ReadAllBytesAsync(filename, BufferSize);

        public static async Task<byte[]> ReadAllBytesAsync(string filename, int bufferSize)
        {
            try
            {
                int length = (int)new FileInfo(filename).Length;
                byte[] bytes = NoBytes;

                using (FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, true))
                {
                    using (MemoryStream memoryStream = new MemoryStream(length))
                    {
                        await stream.CopyToStreamAsync(memoryStream, bufferSize);
                        bytes = memoryStream.ToArray();
                    }
                }
                return bytes;
            }
            catch { return NoBytes; }
        }

        /// <summary>Asynchronously opens a text file, reads all
        /// lines of the file, and then closes the file.</summary>
        /// <param name="path">The file to open for reading.</param>
        /// <returns>A Task that will, on completion of the method,
        /// contain a string array containing all lines of the file.</returns>
        public static async Task<string[]> ReadAllLinesAsync(string path)
        {
            List<string> lines = new List<string>();
            string line = string.Empty;

            using (StreamReader reader = new StreamReader(path, true))
            {
                while ((line = await reader.ReadLineAsync()) != null)
                    lines.Add(line);
            }

            return lines.ToArray();
        }

        /// <summary>Creates a new file, writes the specified byte array to the file asynchronously,
        /// and then closes the file. If the target file already exists, it is overwritten.</summary>
        /// <param name="path">The file to write to.</param>
        /// <param name="bytes">The bytes to write to the file.</param>
        public static async Task WriteAllBytesAsync(string path, byte[] bytes)
        {
            using (MemoryStream memoryStream = new MemoryStream(bytes))
            {
                using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, Constants.BufferSize, true))
                {
                    await memoryStream.CopyToStreamAsync(stream, Constants.BufferSize);
                }
            }
        }

        public static async Task WriteAllBytesAsync(string path, byte[] bytes, int bufferSize)
        {
            using (MemoryStream memoryStream = new MemoryStream(bytes))
            {
                using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize, true))
                {
                    await memoryStream.CopyToStreamAsync(stream, bufferSize);
                }
            }
        }

        /// <summary>Asynchronously creates a new file, writes a collection
        /// of strings to the file, and then closes the file.</summary>
        /// <param name="path">The file to write to.</param>
        /// <param name="contents">The lines to write to the file.</param>
        /// <returns>A Task that represents completion of the method.</returns>
        public static async Task WriteAllLinesAsync(string path, IEnumerable<string> contents) => await WriteAllLinesAsync(path, contents, Encoding.UTF8);

        /// <summary>Asynchronously creates a new file by using the specified encoding,
        /// writes a collection of strings to the file, and then closes the file.</summary>
        /// <param name="path">The file to write to.</param>
        /// <param name="contents">The lines to write to the file.</param>
        /// <param name="encoding">The character encoding to use.</param>
        /// <returns>A Task that represents completion of the method.</returns>
        public static async Task WriteAllLinesAsync(string path, IEnumerable<string> contents, Encoding encoding)
        {
            using (MemoryStream memoryStream = new MemoryStream(contents.SelectMany(s => encoding.GetBytes(s.EndsWith("\r\n") ? s : s.TrimEnd() + "\r\n")).ToArray()))
            {
                using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, Constants.BufferSize, true))
                {
                    await memoryStream.CopyToStreamAsync(stream, Constants.BufferSize);
                }
            }
        }

        /// <summary>Asynchronously creates a new file, writes the specified string to the file,
        /// and then closes the file. If the target file already exists, it is overwritten.
        /// </summary>
        /// <param name="path">The file to write to.</param>
        /// <param name="contents">The string to write to the file.</param>
        /// <returns>A Task that represents completion of the method.</returns>
        public static async Task WriteAllTextAsync(string path, string contents) => await WriteAllLinesAsync(path, new string[] { contents });

        #endregion Public Methods
    }
}
Posted in Programming | Tagged , , , , , , , , , | Leave a comment

How to pause or resume a process with c#

I wrote this post already, but the plugin I used back then formatted the code badly, so here is the subject again… The code shared today is slightly different to the original post since I have been updating my old code to use newer language features. Other than formatting changes, the main difference here is that in the Suspend and Resume methods, Action<T> delegates in the old code have changed to local functions in the updated code. If you’re still using an old version of the .NET Framework or an older version of the compiler or Visual Studio, you can use the old code. Any new code I share going forward will use the newer language features. (Other newer c# language features I use quite widely, though not in this code, are expression-bodied members, the nameof operator, and auto-implemented properties with initializers.)

Assuming that you created and are controlling another process programmatically, a useful feature of the Process API would be a way to “pause” that process. But there isn’t an API to do so, not for processes anyway. To get around this, my Suspend and Resume extension methods to the Process class iterate and then either suspend or resume all threads of the process. I use this in my code when controlling the ffmpeg console application process, which I use to convert videos.

Firstly, it uses another extension method to convert an ICollection to an array. This:

/// <summary>Converts a non-generic collection to an array.</summary>
public static T[] ToArray<T>(this ICollection collection)
{
    T[] items = new T[collection.Count];
    collection.CopyTo(items, 0);

    return items;
}

And here are my Process extensions:

using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading.Tasks;

namespace Romy.Core
{
    /// <summary>Suspend/resume a <see cref="Process"/>
    /// by suspending or resuming all of its threads.</summary>
    /// <remarks><para>
    /// These methods use the <see cref="CollectionExtensions.ToArray&lt;T&gt;"/>
    /// extension method on <b>ICollection</b> to convert a <b>ProcessThreadCollection</b>
    /// to an array.</para>
    /// <para>
    /// The threads are suspended/resumed in parallel for no particular reason, other than the
    /// hope that it may be better to do so as close to all at once as we can.</para></remarks>
    public static class ProcessExtensions
    {
        #region Private Enums

        [Flags]
        private enum ThreadAccess : int
        {
            TERMINATE = (0x0001),
            SUSPEND_RESUME = (0x0002),
            GET_CONTEXT = (0x0008),
            SET_CONTEXT = (0x0010),
            SET_INFORMATION = (0x0020),
            QUERY_INFORMATION = (0x0040),
            SET_THREAD_TOKEN = (0x0080),
            IMPERSONATE = (0x0100),
            DIRECT_IMPERSONATION = (0x0200)
        }

        #endregion Private Enums

        #region Public Methods

        public static void Resume(this Process process)
        {
            void resume(ProcessThread pt)
            {
                IntPtr threadHandle = NativeMethods.OpenThread(
                    ThreadAccess.SUSPEND_RESUME, false, (uint)pt.Id);

                if (threadHandle != IntPtr.Zero)
                {
                    try { NativeMethods.ResumeThread(threadHandle); }
                    finally { NativeMethods.CloseHandle(threadHandle); }
                }
            }

            ProcessThread[] threads = process.Threads.ToArray<ProcessThread>();

            if (threads.Length > 1)
            {
                Parallel.ForEach(threads,
                    new ParallelOptions { MaxDegreeOfParallelism = threads.Length },
                    pt => resume(pt));
            }
            else resume(threads[0]);
        }

        public static void Suspend(this Process process)
        {
            void suspend(ProcessThread pt)
            {
                IntPtr threadHandle = NativeMethods.OpenThread(
                    ThreadAccess.SUSPEND_RESUME, false, (uint)pt.Id);

                if (threadHandle != IntPtr.Zero)
                {
                    try { NativeMethods.SuspendThread(threadHandle); }
                    finally { NativeMethods.CloseHandle(threadHandle); }
                };
            }

            ProcessThread[] threads = process.Threads.ToArray<ProcessThread>();

            if (threads.Length > 1)
            {
                Parallel.ForEach(threads,
                    new ParallelOptions { MaxDegreeOfParallelism = threads.Length },
                    pt => suspend(pt));
            }
            else suspend(threads[0]);
        }

        #endregion Public Methods

        #region Private Classes

        [SuppressUnmanagedCodeSecurity]
        private static class NativeMethods
        {
            #region Public Methods

            [DllImport("kernel32.dll")]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool CloseHandle(IntPtr hObject);

            [DllImport("kernel32.dll")]
            public static extern IntPtr OpenThread(
                ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);

            [DllImport("kernel32.dll")]
            public static extern uint ResumeThread(IntPtr hThread);

            [DllImport("kernel32.dll")]
            public static extern uint SuspendThread(IntPtr hThread);

            #endregion Public Methods
        }

        #endregion Private Classes
    }
}
Posted in Programming | Tagged , , | Leave a comment

Resize an image in c#

Still looking through my old code as I update it for Visual Studio 2019, I found this… I have several extension methods for saving and converting images. Some of them use my async extension methods for handling streams and some are tightly coupled with other of my code, but this one is pretty simple and stands alone. It results in a good quality image resize. I’ll assume you know how to load and save an image and leave that to you…

This is from my now updated code, and uses the c# nameof operator.

This code is designed to be called blindly with the same values for the new width and height, and the maintainAspectRatio flag set to true, so that it can calculate what size to use. That works for me… if you don’t like that, remove the flag parameter and the entire IF block that uses it.

There are other ways to resize an image. This one simply creates a new image of the required size and draws the image passed in onto the new one, and then returns the new image. It works well.

/// <summary>Resizes an image, optionally maintaining width:height ratios.</summary>
/// <param name="image">The <see cref="Image"/> that you wish to resize.</param>
/// <param name="width">The desired width of the resulting image.</param>
/// <param name="height">The desired height of the resulting image.</param>
/// <param name="maintainAspectRatio"><b>True</b> to maintain aspect ratio,
/// otherwise <b>false</b>. This defaults to <b>true</b>.</param>
/// <returns>The resulting resized <see cref="Image"/> object.</returns>
public static Image Resize(this Image image, int width, int height, bool maintainAspectRatio = true)
{
    if (image == null)
        throw new ArgumentNullException(nameof(image));

    if (maintainAspectRatio)
    {
        // calculate resize ratio
        double ratio = (double)width / image.Width;

        if (ratio * image.Height > height)
            ratio = (double)height / image.Height;

        width = (int)Math.Round(ratio * image.Width, MidpointRounding.AwayFromZero);
        height = (int)Math.Round(ratio * image.Height, MidpointRounding.AwayFromZero);
    }

    Bitmap bmp = null;
    try
    {
        bmp = new Bitmap(width, height);

        using (Graphics g = Graphics.FromImage(bmp))
        {
            g.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic;
            g.SmoothingMode = Drawing2D.SmoothingMode.HighQuality;
            g.CompositingQuality = Drawing2D.CompositingQuality.HighQuality;
            g.DrawImage(image, 0, 0, width, height);
        }

        return bmp;
    }
    catch
    {
        if (bmp != null)
            bmp.Dispose();
        throw;
    }
}
Posted in Programming | Tagged | Leave a comment

How to indicate the Windows display is in use (and also enable or disable the screen saver) with c#

Still going through my old code, and I found this…

One part of my application is a video player, but if you’ve ever written one and used it to watch a video that’s more than a couple of minutes or seconds, depending on your power management and screen saver settings, you’ve probably had Windows pull the carpet out from under you and either have the screen saver or sleep mode suddenly take over.

So the code I’m sharing here is intended to solve those two problems.

My video player class has code that simply does this:

        private void MediaViewer_VisibleChanged(object sender, EventArgs e)
        {
            if (Visible)
            {
                // When playing a video, disable screen saver (if it is enabled).
                try
                {
                    userScreenSaverEnabled = ScreenSaver.Enabled;

                    if (userScreenSaverEnabled)
                        ScreenSaver.Enabled = false;
                }
                catch (Exception ex) { ex.Log(); }

                RefreshVolume();

                // Prevent the system from entering standby mode while we are playing video.
                IndicateDisplayInUse();
            }
            else
            {
                // If we disabled the screen saver, enable it again.
                if (userScreenSaverEnabled)
                    ScreenSaver.Enabled = true;

                CloseClip();
            }
        }

Ignore RefreshVolume() and CloseClip(). What I’d like to draw your attention to is:

  1. ScreenSaver.Enabled
  2. IndicateDisplayInUse

Firstly…

Enabling or disabling the screen saver

This one is probably not necessary to most people anymore, because most of us don’t bother with screen savers. Also, when I wrote this, the API way of doing it was buggy in Windows 7, so this code goes directly to the registry.

using Microsoft.Win32;
using System.Linq;

namespace System
{
    /// <summary>Enable/disable the screen saver. </summary>
    /// <remarks><para>
    /// This should be done via calls to <b>SystemParametersInfo</b>, but due to a bug
    /// in Windows 7 where the call to test if the screen saver is active always returns
    /// true, we use the registry directly (as recommended in the MSDN documentation).</para>
    /// <para>
    /// The registry settings are modified in the "Control Panel\Desktop" registry key, and after I
    /// started working at a company that overrides this for all users via a registry setting, it is
    /// also modified, if necessary, in "Software\Policies\Microsoft\Windows\Control Panel\Desktop".
    /// </para></remarks>
    public static class ScreenSaver
    {
        #region Private Fields

        private static readonly RegKeyComparer Comparer = new RegKeyComparer();

        private static string allUsersLastScreenSaver;

        private static string currentUserLastScreenSaver;

        #endregion Private Fields

        #region Public Properties

        public static bool AllUsersPolicyEnabled
        {
            get => IsAllUsersScreenSaverEnabled();
            set
            {
                if (!value)
                {
                    if (IsAllUsersScreenSaverEnabled())
                        RemoveAllUsersScreenSaver();
                }
                else if (!IsAllUsersScreenSaverEnabled() && !string.IsNullOrEmpty(allUsersLastScreenSaver))
                    RestoreAllUsersScreenSaver();
            }
        }

        /// <summary>Enable/disable screen saver.</summary>
        /// <remarks>When the screen saver is enabled, the registry value "Control Panel\Desktop\SCRNSAVE.EXE"
        /// contains the full path to the active screen saver. When disabled, the value does not exist.</remarks>
        public static bool Enabled
        {
            get => IsCurrentUserScreenSaverEnabled();
            set
            {
                if (!value)
                {
                    if (IsCurrentUserScreenSaverEnabled())
                        RemoveCurrentUserScreenSaver();

                    if (IsAllUsersScreenSaverEnabled())
                        RemoveAllUsersScreenSaver();
                }
                else
                {
                    if (!IsCurrentUserScreenSaverEnabled() && !string.IsNullOrEmpty(currentUserLastScreenSaver))
                        RestoreCurrentUserScreenSaver();

                    if (!IsAllUsersScreenSaverEnabled() && !string.IsNullOrEmpty(allUsersLastScreenSaver))
                        RestoreAllUsersScreenSaver();
                }
            }
        }

        #endregion Public Properties

        #region Private Methods

        /// <summary>Checks if there is a policy-defined screen saver for all users.</summary>
        /// <remarks>If such a screen saver is enabled, we store the current value.</remarks>
        /// <returns><b>True</b> if there is a policy-defined screen saver for all users; <b>false</b> otherwise.</returns>
        private static bool IsAllUsersScreenSaverEnabled()
        {
            using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Policies\Microsoft\Windows\Control Panel\Desktop"))
            {
                bool result = key != null && key.GetValueNames().Contains("SCRNSAVE.EXE", Comparer);

                if (result)
                    allUsersLastScreenSaver = (string)key.GetValue("SCRNSAVE.EXE");

                return result;
            }
        }

        /// <summary>Checks if the current user has the screen saver enabled.</summary>
        /// <remarks>If such a screen saver is enabled, we store the current value.</remarks>
        /// <returns><b>True</b> if the current user has a screen saver enabled, otherwise <b>false</b>.</returns>
        private static bool IsCurrentUserScreenSaverEnabled()
        {
            using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop"))
            {
                bool result = key != null && key.GetValueNames().Contains("SCRNSAVE.EXE", Comparer);

                if (result)
                    currentUserLastScreenSaver = (string)key.GetValue("SCRNSAVE.EXE");

                return result;
            }
        }

        private static void RemoveAllUsersScreenSaver()
        {
            using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Policies\Microsoft\Windows\Control Panel\Desktop", true))
            {
                if (key != null)
                {
                    if (key.GetValueNames().Contains("SCRNSAVE.EXE", Comparer))
                        key.DeleteValue("SCRNSAVE.EXE");

                    if (key.GetValueNames().Contains("ScreenSaveTimeOut", Comparer))
                        key.DeleteValue("ScreenSaveTimeOut");

                    if (key.GetValueNames().Contains("ScreenSaverIsSecure", Comparer))
                        key.DeleteValue("ScreenSaverIsSecure");
                }
            }
        }

        private static void RemoveCurrentUserScreenSaver()
        {
            using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", true))
            {
                if (key != null)
                    key.DeleteValue("SCRNSAVE.EXE");
            }
        }

        private static void RestoreAllUsersScreenSaver()
        {
            using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Policies\Microsoft\Windows\Control Panel\Desktop", true))
            {
                if (key != null)
                    key.SetValue("SCRNSAVE.EXE", allUsersLastScreenSaver);
            }
        }

        private static void RestoreCurrentUserScreenSaver()
        {
            using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", true))
            {
                if (key != null)
                    key.SetValue("SCRNSAVE.EXE", currentUserLastScreenSaver);
            }
        }

        #endregion Private Methods
    }
}

Update… I forgot that this code needed a comparer, which is this:

using System.Collections.Generic;

namespace System
{
    /// <summary>A simple <see cref="Collections.Generic.IEqualityComparer&lt;string&gt;"/>
    /// implementation to support a case insensitive comparison of registry values.</summary>
    internal class RegKeyComparer : IEqualityComparer<string>
    {
        public bool Equals(string x, string y)
        {
            return string.Compare(x, y, StringComparison.InvariantCultureIgnoreCase) == 0;
        }

        public int GetHashCode(string obj)
        {
            return (obj as string).GetHashCode();
        }
    }
}

Next…

Indicate the display is in use.

In this case, it’s a simple call to SetThreadExecutionState, with a flag to reset the display idle timer. I like this technique, because it is not a flag that needs to be set back to another state to say we are done. Just call it every now and then in a loop as long as the video player is visible, and don’t call it anymore when we are done.

My method to poll is done via an async timer method I shared years ago, but I’ll include it again here. I call it like this:

protected void IndicateDisplayInUse() => CreatePollingTimer(() => !IsDisposed && Visible, TimeSpan.FromMinutes(1), () => PowerManagement.DisplayIsInUse());

The CreatePollingTimer method is this:

        /// <summary>Starts an asynchronous polling timer, similar to a thread timer.</summary>
        /// <param name="condition">The condition which indicates that polling should continue.</param>
        /// <param name="pollInterval">Similar to a timer tick, this determines the polling frequency.</param>
        /// <param name="pollingAction">The action to be executed after each poll interval, as long as the condition
        /// evaluates to true. This works like a timer event handler.</param>
        /// <param name="afterAction">An optional action to be executed after the condition evaluates to false.</param>
        protected async void CreatePollingTimer(Func<bool> condition, TimeSpan pollInterval, Action pollingAction, Action afterAction = null)
        {
            while (true)
            {
                await Task.Delay(pollInterval);

                if (condition())
                    pollingAction();
                else
                    break;
            }

            afterAction?.Invoke();
        }

You don’t need the above – that’s just how I call it. The actual code to tell the system not to shut down the display is this call to SetThreadExecutionState:

using System.Runtime.InteropServices;
using System.Security;

namespace System
{
    /// <summary>Notifies the system that the display is in use. To be used, for example,
    /// when playing video, since the system is unable to detect video activity alone and
    /// will incorrectly detect that it is idle, and enter sleep mode.</summary>
    public static class PowerManagement
    {
        /// <summary>Informs the system that the display is in use.</summary>
        public static void DisplayIsInUse() => NativeMethods.SetThreadExecutionState(ExecutionStates.DisplayRequired);

        [SuppressUnmanagedCodeSecurity]
        internal static class NativeMethods
        {
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern ExecutionStates SetThreadExecutionState(ExecutionStates esFlags);
        }

        [Flags]
        internal enum ExecutionStates
        {
            /// <summary>No state configured.</summary>
            None = 0,

            /// <summary>Forces the system to be in the working state by resetting the system idle timer.</summary>
            SystemRequired = 0x1,

            /// <summary>Forces the display to be on by resetting the display idle timer.</summary>
            DisplayRequired = 0x2,

            /// <summary>Enables away mode. This value must be specified with ExecutionStates.Continuous.
            /// Windows Server 2003 and Windows XP/2000: ExecutionStates.AwayModeRequired is not supported.</summary>
            AwayModeRequired = 0x40,

            /// <summary>Informs the system that the state being set should remain in effect until the next
            /// call that uses ExecutionStates.Continuous and one of the other state flags is cleared.</summary>
            Continuous = unchecked((int)0x80000000)
        }
    }
}
Posted in Programming | Tagged , , | Leave a comment

Using c# to copy, move, rename files, and delete to the recycle bin via SHFileOperation

While we’re on the subject of deleting files as in the last post, I found this old code of mine in the same solution. I don’t use it anymore because I use IFileOperation instead, but this code still works. (The IFileOperation code wasn’t written by me so I shouldn’t share that – it was some helper classes I found somewhere, written by Stephen Toub of Microsoft.)

Usage should be self explanatory, so here’s the code…

using System.Collections.Generic;
using System.Security;
using System.Text;
using System.Threading.Tasks;

namespace System.Runtime.InteropServices.ComTypes
{
    /// <summary>Helper class, wrapping SHFileOperation, to enable easily deleting to the Windows Recycle bin
    /// from C# without having to reference the Visual Basic Library, as well as copying/moving files with
    /// native Windows progress dialogs.</summary>
    /// <remarks>According to the online help, SHFileOperation calls should be replaced with calls to
    /// IFileOperation from Windows Vista up...</remarks>
    public static class NativeFileOperation
    {
        private const int FO_COPY = 0x0002;

        private const int FO_DELETE = 0x0003;

        private const int FO_MOVE = 0x0001;

        private const int FO_RENAME = 0x0004;

        private const int FOF_ALLOWUNDO = 0x40;

        private const int FOF_NOCONFIRMATION = 0x10;

        private const int FOF_NOCONFIRMMKDIR = 0x0200;

#pragma warning disable IDE0051 // Remove unused private members
        private const int FOF_RENAMEONCOLLISION = 0x0008;
#pragma warning restore IDE0051 // Remove unused private members

        private enum Operation
        {
            Copy,
            Move,
            Delete,
            Rename
        }

        public static bool CopyFiles(IEnumerable<string> files, string destinationDirectory)
        {
            return PerformFileOperation(Operation.Copy, files, destinationDirectory);
        }

        public static bool MoveFiles(IEnumerable<string> files, string destinationDirectory)
        {
            return PerformFileOperation(Operation.Move, files, destinationDirectory);
        }

        private static bool PerformFileOperation(Operation op, IEnumerable<string> files, string destinationDirectory = null)
        {
            StringBuilder builder = new StringBuilder();

            foreach (string filename in files)
            {
                builder.Append(filename + '\0');
            }
            builder.Append('\0');

            SHFILEOPSTRUCT shf = new SHFILEOPSTRUCT
            {
                fFlags = FOF_NOCONFIRMATION,
                pFrom = builder.ToString()
            };

            switch (op)
            {
                case Operation.Copy:
                    shf.wFunc = FO_COPY;
                    shf.fFlags |= FOF_NOCONFIRMMKDIR;
                    shf.pTo = destinationDirectory.TrimEnd() + "\0\0";
                    break;
                case Operation.Delete:
                    shf.wFunc = FO_DELETE;
                    shf.fFlags |= FOF_ALLOWUNDO;
                    shf.pTo = null;
                    break;
                case Operation.Move:
                    shf.fFlags |= FOF_NOCONFIRMMKDIR;
                    shf.wFunc = FO_MOVE;
                    shf.pTo = destinationDirectory.TrimEnd() + "\0\0";
                    break;
                case Operation.Rename:
                    shf.fFlags |= FOF_NOCONFIRMMKDIR;
                    shf.fFlags |= FOF_ALLOWUNDO;
                    shf.wFunc = FO_RENAME;
                    shf.pTo = destinationDirectory.TrimEnd() + "\0\0";
                    break;
            }

            return NativeMethods.SHFileOperation(ref shf) == 0;
        }

        [SuppressUnmanagedCodeSecurity]
        internal static class NativeMethods
        {
            [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
            internal static extern int SHFileOperation(ref SHFILEOPSTRUCT FileOp);
        }

        public static async Task<bool> CopyFileAsync(string fileName, string destinationDirectory) => await CopyFilesAsync(new string[] { fileName }, destinationDirectory);

        public static async Task<bool> CopyFilesAsync(IEnumerable<string> files, string destinationDirectory) => await PerformFileOperationAsync(Operation.Copy, files, destinationDirectory);

        public static async Task<bool> DeleteToRecycleBinAsync(string fileName) => await DeleteToRecycleBinAsync(new string[] { fileName });

        public static async Task<bool> DeleteToRecycleBinAsync(IEnumerable<string> files) => await PerformFileOperationAsync(Operation.Delete, files);

        public static async Task<bool> MoveFileAsync(string fileName, string destinationDirectory) => await MoveFilesAsync(new string[] { fileName }, destinationDirectory);

        public static async Task<bool> MoveFilesAsync(IEnumerable<string> files, string destinationDirectory) => await PerformFileOperationAsync(Operation.Move, files, destinationDirectory);

        public static async Task<bool> RenameFileAsync(string filename, string destinationDirectory) => await PerformFileOperationAsync(Operation.Rename, new string[] { filename }, destinationDirectory);

        public static async Task<bool> RenameFilesAsync(IEnumerable<string> files, string destinationDirectory) => await PerformFileOperationAsync(Operation.Rename, files, destinationDirectory);

        private static async Task<bool> PerformFileOperationAsync(Operation op, IEnumerable<string> files, string destinationDirectory = null)
        {
            return await Task.Run(() =>
            {
                StringBuilder builder = new StringBuilder();

                foreach (string filename in files)
                {
                    builder.Append(filename + '\0');
                }
                builder.Append('\0');

                SHFILEOPSTRUCT shf = new SHFILEOPSTRUCT
                {
                    fFlags = FOF_NOCONFIRMATION,
                    pFrom = builder.ToString()
                };

                switch (op)
                {
                    case Operation.Copy:
                        shf.wFunc = FO_COPY;
                        shf.fFlags |= FOF_NOCONFIRMMKDIR;
                        shf.pTo = destinationDirectory.TrimEnd() + "\0\0";
                        break;
                    case Operation.Delete:
                        shf.wFunc = FO_DELETE;
                        shf.fFlags |= FOF_ALLOWUNDO;
                        shf.pTo = null;
                        break;
                    case Operation.Move:
                        shf.fFlags |= FOF_NOCONFIRMMKDIR;
                        shf.wFunc = FO_MOVE;
                        shf.pTo = destinationDirectory.TrimEnd() + "\0\0";
                        break;
                    case Operation.Rename:
                        shf.fFlags |= FOF_NOCONFIRMMKDIR;
                        shf.fFlags |= FOF_ALLOWUNDO;
                        shf.wFunc = FO_RENAME;
                        shf.pTo = destinationDirectory.TrimEnd() + "\0\0";
                        break;
                }

                return NativeMethods.SHFileOperation(ref shf) == 0;
            });
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct SHFILEOPSTRUCT
        {
            private readonly IntPtr hwnd;

            [MarshalAs(UnmanagedType.U4)]
            public int wFunc;

            public string pFrom;

            public string pTo;

            public short fFlags;

            [MarshalAs(UnmanagedType.Bool)]
            public bool fAnyOperationsAborted;

            private readonly IntPtr hNameMappings;

            public string lpszProgressTitle;
        }
    }
}
Posted in General, Programming | Tagged , , | Leave a comment

Some slight overkill for a method that deletes files and directories

Still cleaning up and improving my old code… I found this. It’s a method in my base class for console applications, to handle deleting files. This is something I’ve approached in different ways in different places – even in the same solution. Not sure how useful it is, but regardless, this method does it’s job, which is to delete files or directories and ignore any exceptions. LogError code not included so if you use this code, you need to decide what to do there…

        /// <summary>Delete the file or directory specified,
        /// without throwing an exception on failure.</summary>
        public static bool DeleteFileSystemObject(string path)
        {
            try
            {
                /* File.Exists seems to return true for directories,
                 * so always check with Directory.Exists first... */
                if (Directory.Exists(path))
                {
                    DirectoryInfo info = new DirectoryInfo(path);

                    // If path == current directory, change the current directory.
                    if (string.Compare(Directory.GetCurrentDirectory(), path, StringComparison.InvariantCultureIgnoreCase) == 0 && Directory.Exists(Path.GetDirectoryName(path)))
                        Directory.SetCurrentDirectory(Path.GetDirectoryName(path));

                    // Try to silently delete readonly/hidden directories.
                    if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly || (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                        info.Attributes = FileAttributes.Directory & FileAttributes.Archive;

                    Directory.Delete(path, true);
                }
                else if (File.Exists(path))
                {
                    // Try to silently delete readonly/hidden files.
                    if ((File.GetAttributes(path) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly || (File.GetAttributes(path) & FileAttributes.Hidden) == FileAttributes.Hidden)
                        File.SetAttributes(path, FileAttributes.Normal);

                    File.Delete(path);
                }
                return true;
            }
            catch (IOException) { }
            catch (UnauthorizedAccessException) { }
            catch (Exception ex) { LogError(ex); }
            return false;
        }
Posted in General, Programming | Tagged , | Leave a comment

Using a Folder Browser Common Dialog in c#

Lately I have been taking my old solution, which contains a large amount of my personal and experimental code, and updating it for Visual Studio 2019, since there are several wizards which help people like me who have missed all the recent language improvements, to be able to apply them.

Anyway, along the way I’ve stumbled on code I wrote years ago that might be useful for others. Today I’m sharing my implementation of the Folder Browser Common Dialog. And by Folder Browser Common Dialog, I mean the standard file selection dialog, but in folder mode, which is available in Windows but not implemented in .Net, which instead gives us the flimsy “Browse For Folder” dialog, which contains only a TreeView and doesn’t look like a standard dialog.

First, here’s an example that uses it, taken from my monolithic solution.

using Romy.Core.Dialogs;
using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;

namespace Romy.UI
{
    public class FolderPathEditor : UITypeEditor
    {
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.Modal;
        }

        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            FolderBrowser browser = new FolderBrowser();
            if (value != null)
                browser.Folder = string.Format("{0}", value);

            if (browser.ShowDialog(null) == DialogResult.OK)
                return browser.Folder;

            return value;
        }
    }
}

You may have noticed that class is a UITypeEditor – that’s because I use it from my options dialog, which has a property grid. So I can then browse for a folder from a property grid. It looks like this:

SNAGHTML24fafe3b

The dialog itself, as displayed when you click the ellipses next to the Cache Directory value in that property grid, is this:

SNAGHTML24fbc3ec

So, here’s the code for the dialog… The actual code is very little and all in the ShowDialog method, while the rest is all the platform invoked code and declarations needed to hook it all up. Use at your own risk… All of this was coded years ago when I was a meth addict. (Honestly, I don’t have the patience to write this sort of code when I’m clean, which I have been since September 2013 by the way.)

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Security;
using System.Windows.Forms;

namespace Romy.Core.Dialogs
{
    /// <summary>Wraps the Common Item Dialog with flags to use it as a folder browser, since this
    /// is not implemented in managed code and I do not want to use the Windows API CodePack.</summary>
#pragma warning disable CA1712 // Do not prefix enum values with type name
    public class FolderBrowser
    {
        #region Internal Enums

        internal enum FDAP
        {
            FDAP_BOTTOM = 0,
            FDAP_TOP = 1
        }

        internal enum FDE_OVERWRITE_RESPONSE
        {
            FDEOR_DEFAULT = 0x00000000,
            FDEOR_ACCEPT = 0x00000001,
            FDEOR_REFUSE = 0x00000002
        }

        internal enum FDE_SHAREVIOLATION_RESPONSE
        {
            FDESVR_DEFAULT = 0x00000000,
            FDESVR_ACCEPT = 0x00000001,
            FDESVR_REFUSE = 0x00000002
        }

        [Flags]
        internal enum FOS : uint
        {
            FOS_OVERWRITEPROMPT = 0x00000002,
            FOS_STRICTFILETYPES = 0x00000004,
            FOS_NOCHANGEDIR = 0x00000008,
            FOS_PICKFOLDERS = 0x00000020,
            FOS_FORCEFILESYSTEM = 0x00000040, // Ensure that items returned are filesystem items.
            FOS_ALLNONSTORAGEITEMS = 0x00000080, // Allow choosing items that have no storage.
            FOS_NOVALIDATE = 0x00000100,
            FOS_ALLOWMULTISELECT = 0x00000200,
            FOS_PATHMUSTEXIST = 0x00000800,
            FOS_FILEMUSTEXIST = 0x00001000,
            FOS_CREATEPROMPT = 0x00002000,
            FOS_SHAREAWARE = 0x00004000,
            FOS_NOREADONLYRETURN = 0x00008000,
            FOS_NOTESTFILECREATE = 0x00010000,
            FOS_HIDEMRUPLACES = 0x00020000,
            FOS_HIDEPINNEDPLACES = 0x00040000,
            FOS_NODEREFERENCELINKS = 0x00100000,
            FOS_DONTADDTORECENT = 0x02000000,
            FOS_FORCESHOWHIDDEN = 0x10000000,
            FOS_DEFAULTNOMINIMODE = 0x20000000
        }

        internal enum SIATTRIBFLAGS
        {
            SIATTRIBFLAGS_AND = 1,
            SIATTRIBFLAGS_APPCOMPAT = 3,
            SIATTRIBFLAGS_OR = 2
        }

        internal enum SIGDN : uint
        {
            SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000,
            SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000,
            SIGDN_FILESYSPATH = 0x80058000,
            SIGDN_NORMALDISPLAY = 0,
            SIGDN_PARENTRELATIVE = 0x80080001,
            SIGDN_PARENTRELATIVEEDITING = 0x80031001,
            SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001,
            SIGDN_PARENTRELATIVEPARSING = 0x80018001,
            SIGDN_URL = 0x80068000
        }

        #endregion Internal Enums

        #region Public Properties

        public string Folder { get; set; }

        public string Title { get; set; }

        #endregion Public Properties

        #region Public Methods

        public DialogResult ShowDialog(IWin32Window owner)
        {
            IntPtr hwndOwner = owner != null ? owner.Handle : NativeMethods.GetActiveWindow();

            NativeInterfaces.IFileOpenDialog dialog = (NativeInterfaces.IFileOpenDialog)new FileOpenDialog();
            try
            {
                NativeInterfaces.IShellItem item;

                if (!string.IsNullOrEmpty(Folder))
                {
                    Guid _shellItemGuid = typeof(NativeInterfaces.IShellItem).GUID;
                    item = (NativeInterfaces.IShellItem)NativeMethods.SHCreateItemFromParsingName(Folder, null, ref _shellItemGuid);

                    if (item != null)
                        dialog.SetFolder(item);
                }

                dialog.SetOptions(FOS.FOS_PICKFOLDERS | FOS.FOS_FORCEFILESYSTEM);

                if (!string.IsNullOrEmpty(Title))
                    dialog.SetTitle(Title);

                int hr = dialog.Show(hwndOwner);
                if (hr == NativeMethods.ERROR_CANCELLED)
                    return DialogResult.Cancel;

                if (hr != 0)
                    return DialogResult.Abort;

                dialog.GetResult(out item);

                item.GetDisplayName(SIGDN.SIGDN_FILESYSPATH, out string path);
                Folder = path;

                return DialogResult.OK;
            }
            finally { Marshal.ReleaseComObject(dialog); }
        }

        #endregion Public Methods

        #region Internal Structs

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
        internal struct COMDLG_FILTERSPEC
        {
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pszName;

            [MarshalAs(UnmanagedType.LPWStr)]
            public string pszSpec;
        }

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        internal struct PROPERTYKEY
        {
            public Guid fmtid;

            public uint pid;
        }

        #endregion Internal Structs

        #region Internal Classes

        internal static class NativeInterfaces
        {
            #region Public Interfaces

            [ComImport, Guid("42f85136-db7e-439c-85f1-e4075d135fc8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IFileDialog : IModalWindow
            {
                [PreserveSig]
                new int Show([In] IntPtr parent);

                void SetFileTypes([In] uint cFileTypes, [In] [MarshalAs(UnmanagedType.LPArray)]COMDLG_FILTERSPEC[] rgFilterSpec);

                void SetFileTypeIndex([In] uint iFileType);

                void GetFileTypeIndex(out uint piFileType);

                void Advise([In, MarshalAs(UnmanagedType.Interface)] IFileDialogEvents pfde, out uint pdwCookie);

                void Unadvise([In] uint dwCookie);

                void SetOptions([In] FOS fos);

                void GetOptions(out FOS pfos);

                void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);

                void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);

                void GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

                void GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

                void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName);

                void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);

                void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle);

                void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText);

                void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel);

                void GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

                void AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, int alignment);

                void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);

                void Close([MarshalAs(UnmanagedType.Error)] int hr);

                void SetClientGuid([In] ref Guid guid);

                void ClearClientData();

                void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter);
            }

            [ComImport, Guid("973510DB-7D7F-452B-8975-74A85828D354"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IFileDialogEvents
            {
                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig]
                int OnFileOk([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig]
                int OnFolderChanging([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd,
                             [In, MarshalAs(UnmanagedType.Interface)] IShellItem psiFolder);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void OnFolderChange([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void OnSelectionChange([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void OnShareViolation([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd,
                          [In, MarshalAs(UnmanagedType.Interface)] IShellItem psi,
                          out FDE_SHAREVIOLATION_RESPONSE pResponse);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void OnTypeChange([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void OnOverwrite([In, MarshalAs(UnmanagedType.Interface)] IFileDialog pfd,
                         [In, MarshalAs(UnmanagedType.Interface)] IShellItem psi,
                         out FDE_OVERWRITE_RESPONSE pResponse);
            }

            [ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IFileOpenDialog : IFileDialog
            {
                void AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, FileDialogCustomPlace fdcp);

                void GetResults([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppenum);

                void GetSelectedItems([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppsai);
            }

            [ComImport, Guid("b4db1657-70d7-485e-8e3e-6fcb5a5c1802"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IModalWindow
            {
                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime), PreserveSig]
                uint Show([In] IntPtr parent);
            }

            [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IShellItem
            {
                void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid bhid, [In] ref Guid riid, out IntPtr ppv);

                void GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

                void GetDisplayName([In] SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);

                void GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs);

                void Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder);
            }

            [ComImport, Guid("B63EA76D-1F85-456F-A19C-48159EFA858B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IShellItemArray
            {
                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid rbhid,
                        [In] ref Guid riid, out IntPtr ppvOut);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void GetPropertyStore([In] int Flags, [In] ref Guid riid, out IntPtr ppv);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void GetPropertyDescriptionList([In] ref PROPERTYKEY keyType, [In] ref Guid riid, out IntPtr ppv);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void GetAttributes([In] SIATTRIBFLAGS dwAttribFlags, [In] uint sfgaoMask, out uint psfgaoAttribs);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void GetCount(out uint pdwNumItems);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void GetItemAt([In] uint dwIndex, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);

                [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
                void EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems);
            }

            #endregion Public Interfaces
        }

        [SuppressUnmanagedCodeSecurity]
        internal static class NativeMethods
        {
            #region Public Fields

            public const int ERROR_CANCELLED = unchecked((int)0x800704C7);

            #endregion Public Fields

            #region Public Methods

            [DllImport("user32.dll")]
            public static extern IntPtr GetActiveWindow();

            [DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode, PreserveSig = false)]
            [return: MarshalAs(UnmanagedType.Interface)]
            public static extern object SHCreateItemFromParsingName(
                [MarshalAs(UnmanagedType.LPWStr)] string pszPath, IBindCtx pbc, ref Guid riid);

            #endregion Public Methods
        }

        #endregion Internal Classes

        #region Private Classes

        [ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")]
        private class FileOpenDialog { }

        #endregion Private Classes
    }
#pragma warning restore CA1712 // Do not prefix enum values with type name
}
Posted in Programming | Tagged , , | Leave a comment

Saving a collection of images to a high quality GIF animation using c#

I ran into an interesting problem this weekend… I was showing off a feature of my code to my son, and it didn’t work as expected…

I have this application with years of my personal code, which I have recently updated to use the latest version of Visual Studio (2019). I was showing him how it views a GIF I downloaded, but he complained that the animation was too slow. So, I used my old trusty code to extract the frames of the GIF, then encoded it back to GIF format using a shorter frame delay (with the Gif Encoder I downloaded years ago). But, although everything worked, the image looked shitty, because the encoder uses Gif format for each frame.

Here follows my solution for this annoying problem.

For this code to work, you will need:

  1. Bumpkit.GifEncoder. Download here.
  2. AForge.Net image library. Download here.

My code now works around the issue by reducing the colours of each Bitmap before adding a frame via the Gif encoder.

The code below should be self-explanatory. The ExtractFrames method at the bottom uses standard .Net Framework libraries to extract all the frames from an animated Gif file. I call it from a user interface and then save all the frames to a directory. (That code not shown.)

The other two methods, SaveAnimatedGifImage, and its async counterpart, are the ones I’m focusing on here. I worked around the loss of image quality problem by using a colour quantizer from the AForge.Net image library to reduce each image to 256 colours before adding a frame with the Gif encoder.

The resulting image looked like this:

giphy

And here is the code:

using AForge.Imaging.ColorReduction;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace Romy.Core
{
    public static class GifExtensions
    {
        /// <summary>Saves the images as frames to an animated Gif Image.</summary>
        /// <param name="images">The images to save.</param>
        /// <param name="path">The path of the Gif file to create.</param>
        /// <param name="delay">The delay between frames, in milliseconds.</param>
        /// <param name="repeat">The number of times the animation should repeat. Leave this zero
        /// for it to loop forever, or specify a value to limit the number of repetitions.</param>
        public static void SaveAnimatedGifImage(this IEnumerable<Image> images, string path, int delay = 100, int repeat = 0)
        {
            var quantizer = new ColorImageQuantizer(new MedianCutQuantizer())
            {
                UseCaching = true
            };

            using (MemoryStream stream = new MemoryStream())
            {
                using (BumpKit.GifEncoder encoder = new BumpKit.GifEncoder(stream, null, null, repeat))
                {
                    Image[] imageArray = images.ToArray();

                    for (int i = 0; i < imageArray[0].Height; i++)
                    {
                        for (int j = 0; j < imageArray[0].Width; j++)
                        {
                            quantizer.Quantizer.AddColor(
                                (imageArray[0] as Bitmap).GetPixel(j, i));
                        }
                    }

                    for (int i = 0; i < imageArray.Length; i++)
                    {
                        var image = quantizer.ReduceColors(imageArray[i] as Bitmap, 256);
                        encoder.AddFrame((Image)image.CopyImage(), 0, 0, TimeSpan.FromMilliseconds(delay));
                    }
                }

                stream.Position = 0;

                using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, Constants.BufferSize, false))
                {
                    stream.WriteTo(fileStream);
                }
            }
        }

        /// <summary>Asynchronously saves the images as frames to an animated Gif Image.</summary>
        /// <param name="images">The images to save.</param>
        /// <param name="path">The path of the Gif file to create.</param>
        /// <param name="delay">The delay between frames, in milliseconds.</param>
        /// <param name="repeat">The number of times the animation should repeat. Leave this zero
        /// for it to loop forever, or specify a value to limit the number of repetitions.</param>
        public static async Task SaveAnimatedGifImageAsync(this IEnumerable<Image> images, string path, int delay = 100, int repeat = 0)
        {
            var quantizer = new ColorImageQuantizer(new MedianCutQuantizer())
            {
                UseCaching = true
            };

            using (MemoryStream stream = new MemoryStream())
            {
                using (BumpKit.GifEncoder encoder = new BumpKit.GifEncoder(stream, null, null, repeat))
                {
                    Image[] imageArray = images.ToArray();

                    for (int i = 0; i < imageArray[0].Height; i++)
                    {
                        for (int j = 0; j < imageArray[0].Width; j++)
                        {
                            quantizer.Quantizer.AddColor(
                                (imageArray[0] as Bitmap).GetPixel(j, i));
                        }
                    }

                    for (int i = 0; i < imageArray.Length; i++)
                    {
                        var image = quantizer.ReduceColors(imageArray[i] as Bitmap, 256);
                        encoder.AddFrame((Image)image.CopyImage(), 0, 0, TimeSpan.FromMilliseconds(delay));
                    }
                }

                stream.Position = 0;

                using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, Constants.BufferSize, true))
                {
                    await stream.CopyToStreamAsync(fileStream, Constants.BufferSize);
                }
            }
        }

        /// <summary>Extracts the frames and the delay for each frame from an animated Gif Image.</summary>
        /// <returns>An enumerable of key-value pairs, where the key is the image, and the value is that
        /// frame's delay, in milliseconds.</returns>
        public static IEnumerable<KeyValuePair<Image, int>> ExportFrames(this Image gifImage)
        {
            if (gifImage.RawFormat.Equals(ImageFormat.Gif) && ImageAnimator.CanAnimate(gifImage))
            {
                FrameDimension frameDimension = new FrameDimension(gifImage.FrameDimensionsList[0]);
                int frameCount = gifImage.GetFrameCount(frameDimension);
                int index = 0;

                for (int i = 0; i < frameCount; i++)
                {
                    int delay = BitConverter.ToInt32(gifImage.GetPropertyItem(20736).Value, index) * 10;
                    index += Marshal.SizeOf(index);

                    gifImage.SelectActiveFrame(frameDimension, i);
                    yield return new KeyValuePair<Image, int>(gifImage.CopyImage(), delay);
                }
            }
        }
    }
}

Oops, I see the method to extract frames uses my alternative to Clone() to make a copy of the image. Maybe that is what went wrong in the first place? Anyway, you could just change it to use Clone, but if you want my extension method so the above will compile as is, here you go… (Btw, my CopyImage extension method exists to avoid the dreaded “A generic error occurred in GDI+” error message.)

Regardless, if you’re not doing something contrived like encoding frames extracted from an existing GIF but with a shorter frame delay, but rather encoding frames you got from a video, the colour reduction algorithm might be useful anyway.

        /// <summary>Creates a 24 bit-per-pixel copy of the source image.</summary>
        public static Image CopyImage(this Image image) => CopyImage(image, PixelFormat.Format24bppRgb);

        /// <summary>Creates a copy of the source image with the specified pixel format.</summary><remarks>
        /// This can also be achieved with the <see cref="Bitmap.Clone(int, int, PixelFormat)"/>
        /// overload, but I have had issues with that method.</remarks>
        public static Image CopyImage(this Image image, PixelFormat format)
        {
            if (image == null)
                throw new ArgumentNullException("image");

            // Don't try to draw a new Bitmap with an indexed pixel format.
            if (format == PixelFormat.Format1bppIndexed || format == PixelFormat.Format4bppIndexed || format == PixelFormat.Format8bppIndexed || format == PixelFormat.Indexed)
                return (image as Bitmap).Clone(new Rectangle(0, 0, image.Width, image.Height), format);

            Image result = null;
            try
            {
                result = new Bitmap(image.Width, image.Height, format);

                using (Graphics graphics = Graphics.FromImage(result))
                {
                    graphics.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic;
                    graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias;
                    graphics.CompositingQuality = Drawing2D.CompositingQuality.HighQuality;

                    graphics.DrawImage(image, 0, 0, result.Width, result.Height);
                }
            }
            catch
            {
                if (result != null)
                    result.Dispose();

                throw;
            }
            return result;
        }

And another oops… I see the async method used my own Stream copying extension method. I wrote about it a while back and you could just use the built-in Stream.CopyToAsync(Stream) method, but here is my one again anyway…

        /// <summary>An implementation to copy asynchronously from one stream to another,
        /// similar to <see cref="Stream.CopyToAsync(Stream)"/></summary>
        /// <remarks>
        /// This was written because the default implementation would sometimes throw OutOfMemoryExceptions
        /// in my FileAsync.ReadAllBytesAsync method, when opening large Bitmap files. Reading with a fixed
        /// size small buffer works well as a general solution, though I have also begun using a larger
        /// buffer to improve performance with larger files.</remarks>
        public static async Task CopyToStreamAsync(this Stream source, Stream destination, int bufferSize)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            if (destination == null)
                throw new ArgumentNullException("destination");

            if (bufferSize <= 0)
                throw new ArgumentOutOfRangeException("bufferSize", "bufferSize must be greater than zero");

            /* The source stream may not support seeking; e.g. a stream
             * returned by ZipArchiveEntry.Open() or a network stream. */
            int size = bufferSize;
            bool canSeek = source.CanSeek;

            if (canSeek)
            {
                try
                {
                    size = (int)Math.Min(bufferSize, source.Length);
                }
                catch (NotSupportedException) { canSeek = false; }
            }

            byte[] buffer = new byte[size];
            long remaining = canSeek ? source.Length : 0;

            /* If the stream is seekable, seek through it until all bytes are read.
             * If we read less than the expected number of bytes, it indicates an
             * error, so throw the appropriate exception.
             *
             * If the stream is not seekable, loop until we read 0 bytes. (It's not
             * an error in this case.) */
            while (!canSeek || remaining > 0)
            {
                int read = await source.ReadAsync(buffer, 0, size);

                if (read <= 0)
                {
                    if (canSeek)
                        throw new EndOfStreamException(
                            string.Format("End of stream reached, but {0} remained to be read.",
                            FormatBytes(remaining)));
                    else
                        break;
                }

                await destination.WriteAsync(buffer, 0, read);
                remaining -= canSeek ? read : 0;
            }
        }

Oh no… I guess you need this too:

        private static string FormatBytes(long bytes)
        {
            const long KiloByte = 1024L;
            const long MegaByte = KiloByte * KiloByte;
            const long GigaByte = MegaByte * KiloByte;
            const long TeraByte = GigaByte * KiloByte;
            const long PetaByte = TeraByte * KiloByte;
            const long ExaByte = PetaByte * KiloByte;

            string formattedBytes = string.Empty;

            if (bytes < KiloByte)
                formattedBytes = string.Format("{0:F2} bytes", bytes);
            else if (bytes >= KiloByte && bytes < MegaByte)
                formattedBytes = string.Format("{0:F2} KB", Math.Round((double)bytes / KiloByte, 2, MidpointRounding.AwayFromZero));
            else if (bytes >= MegaByte && bytes < GigaByte)
                formattedBytes = string.Format("{0:F2} MB", Math.Round((double)bytes / MegaByte, 2, MidpointRounding.AwayFromZero));
            else if (bytes >= GigaByte && bytes < TeraByte)
                formattedBytes = string.Format("{0:F2} GB", Math.Round((double)bytes / GigaByte, 2, MidpointRounding.AwayFromZero));
            else if (bytes >= TeraByte && bytes < PetaByte)
                formattedBytes = string.Format("{0:F2} TB", Math.Round((double)bytes / TeraByte, 2, MidpointRounding.AwayFromZero));
            else if (bytes >= PetaByte && bytes < ExaByte)
                formattedBytes = string.Format("{0:F2} PB", Math.Round((double)bytes / PetaByte, 2, MidpointRounding.AwayFromZero));
            else if (bytes >= ExaByte)
                formattedBytes = string.Format("{0:F2} EB", Math.Round((double)bytes / ExaByte, 2, MidpointRounding.AwayFromZero));

            return formattedBytes;
        }

Posted in Programming | Tagged , | Leave a comment

Locating and running msbuild from the command line in Visual Studio 2019

Yesterday I installed Visual Studio 2019 Community Edition on my home machine. I went straight up from 2015 to 2019, and lo and behold, my build script, for my sample app that contains all my experimental code, no longer works. This is because there is no longer an environment variable pointing to the install location, which in my case sits in G:\Program Files…

My build script, which worked for years, always looked something like this:

@ECHO OFF
CALL "%VS140COMNTOOLS%\vsvars32.bat"
msbuild RomyView.sln /t:Rebuild /m /nologo /v:m /p:Configuration=Release

Of course this now results in the old familiar error: ‘msbuild’ is not recognized as an internal or external command, operable program or batch file.

So off to Stack Overflow I go… I found some rather useless comments on it, like one twat who wrote, “Just run the developer command prompt from the start menu”. Like, seriously? When all I want to do is double-click a batch file in my directory that’s worked for fucking years?

This one has the answer, which I have modified slightly for my own script. It turns out that although the install path may vary, the Visual Studio installer path is fixed, and it has a utility called vswhere that you can query for the Visual Studio path. My script now looks like this:

@ECHO OFF

set INSTALLPATH=

if exist "%programfiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" (
  for /F "tokens=* USEBACKQ" %%F in (`"%programfiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -version 16.0 -property installationPath`) do set INSTALLPATH=%%F
)

call "%INSTALLPATH%\Common7\Tools\VsDevCmd.bat"
msbuild RomyView.sln /t:Rebuild /m /nologo /v:m /p:Configuration=Release

I hope you find this or the relevant Stack Overflow Q & A before you get as annoyed as I did.

Posted in Programming | Tagged | Leave a comment

Easily draw semi-transparent text on an image in c#

I had a requirement to return an image with text on it, and it turns out this is really easy to do in c#.

My code is running in a WCF service so my input and outputs are base64-encoded strings, but if you don’t need that part, it should be easy enough to change this code as you need.

        private static string DrawTextOnImage(string inputImage)
        {
            string modifiedImage = string.Empty;

            using (var stream = new MemoryStream(Convert.FromBase64String(inputImage)))
            {
                using (Image image = Image.FromStream(stream))
                {
                    using (Graphics graphics = Graphics.FromImage(image))
                    {
                        graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
                        graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

                        using (Font font = new Font("Arial", 24, FontStyle.Bold))
                        {
                            string text = "Copyright © 2019";

                            // Measure string to figure out the width needed.
                            SizeF stringSize = graphics.MeasureString(text, font);

                            /* Draw twice, first in transparent black and then 
                             * transparent white, so we have a shadow effect. */
                            using (SolidBrush shadowBrush = new SolidBrush(Color.FromArgb(100, 0, 0, 0)),
                                textBrush = new SolidBrush(Color.FromArgb(100, 255, 255, 255)))
                            {
                                float x = (image.Width - stringSize.Width) / 2F;
                                float y = image.Height / 2F;

                                graphics.DrawString(text, font, shadowBrush, new PointF(x + 1, y + 1));
                                graphics.DrawString(text, font, textBrush, new PointF(x, y));
                            }
                        }
                    }

                    // Save image to file for testing
                    image.Save(@"C:\Temp\Test.jpg", ImageFormat.Jpeg);

                    // Convert the image back to a base64 encoded string
                    using (MemoryStream m = new MemoryStream())
                    {
                        image.Save(m, ImageFormat.Jpeg);
                        modifiedImage = Convert.ToBase64String(m.ToArray());
                    }
                }
            }
            return modifiedImage;
        }
Posted in Programming | Tagged , | Leave a comment

Subclassing Exception to log some contextual information

Today I arrived at work to find some truly wonderful emails in my inbox, containing the informative error:

String or binary data would be truncated.

So somewhere in a public method, one of the members of a structure in scope was too big for the database field we tried to save it to, and something somewhere was unable to save. Easy to fix if I know what method and what field it was, but I know nothing of the sort. Now what?

The error handlers in this application simply email the error message to everyone in the team. I needed a quick and dirty way to extend them such that they logged whatever relevant contextual information was available in the scope of the method that broke.

I decided to subclass Exception, adding a collection of objects to my custom exception, then have a LogError method that returns a string, on my derived class, so that I can simply call it in the methods that handle exceptions and send emails. It worked out quite nicely.

Unlike last time, I can share this code, because this time it is all downloaded from StackOverflow. I used the code from…

What this code does is, it logs every error message from the topmost Exception and every inner Exception, and then lists all of the instances added to the contextInfo parameter by class name, with the name and value of each public property of those instances on a new line. This was more than enough to tell me exactly what the issue was.

Here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SampleWcfService
{
    internal static class ExceptionExtensions
    {
        public static IEnumerable<TSource> FromHierarchy<TSource>(this TSource source,
            Func<TSource, TSource> nextItem, Func<TSource, bool> canContinue)
        {
            for (var current = source; canContinue(current); current = nextItem(current))
            {
                yield return current;
            }
        }

        public static IEnumerable<TSource> FromHierarchy<TSource>(this TSource source,
            Func<TSource, TSource> nextItem) where TSource : class
        {
            return FromHierarchy(source, nextItem, s => s != null);
        }

        public static string GetaAllMessages(this Exception exception)
        {
            var messages = exception.FromHierarchy(ex => ex.InnerException)
                .Select(ex => ex.Message);
            return String.Join(Environment.NewLine, messages);
        }
    }

    public class SampleServiceException : Exception
    {
        private List<object> contextInfo = new List<object>();

        public SampleServiceException(string message, Exception innerException, List<object> contextInfo)
            : base(message, innerException)
        {
            this.contextInfo = contextInfo;
        }

        public string LogError()
        {
            StringBuilder builder = new StringBuilder();

            builder.AppendLine("Exception: " + this.GetaAllMessages());
            builder.AppendLine();

            if (contextInfo.Count > 0)
            {
                foreach (var obj in contextInfo)
                {
                    builder.AppendLine(obj.GetType().Name + ":");

                    foreach (var prop in obj.GetType().GetProperties())
                    {
                        builder.AppendLine(prop.Name + " = " + prop.GetValue(obj, null));
                    }
                    builder.AppendLine();
                }
            }

            return builder.ToString();
        }
    }
}

And here is an example of its usage. For brevity, I have omitted error checking and null checking. In practice it may be better to, for example, check that everything you pass to the context info collection is valid, and if you have collections whose properties you need to see, iterate them and pass the members you need…

        public Response ExampleMethod(Request request)
        {
            Response response = new Response();

            try
            {
                try
                {
                    // Do something here that populates response...
                }
                catch (Exception ex)
                {
                    List<object> contextInfo = new List<object>();

                    contextInfo.Add(request);
                    contextInfo.Add(response);

                    throw new SampleServiceException("Unexpected error in ExampleMethod.", ex, contextInfo);
                }
            }
            catch (SampleServiceException sex)
            {
                var errorBody = sex.LogError();

                // Code to notify here, using email or whatever...
            }

            return response;
        }
Posted in Programming | Tagged , | Leave a comment

Sorry I took that last post down…

This past Monday I published a post with a helper class for relaying HTTP POST requests from one WCF service to another, detailing how to create a relay service in c#. Then I took it down right away. Sorry about that…

Here’s what happened: I wrote the code to solve a problem at work on the Friday before that. It took a couple of hours and the code was written inside my WCF service implementation. Then on the Sunday when I wrote the blog post, I refactored that code into a generic helper class, making it different to my code at work. The helper class changed the WCF service such that each endpoint’s code became a one line call to the helper, which was why I saw value in sharing it. Then I scheduled the blog post for 5PM (South African time) on Monday.

However, on Monday I refactored my work code to use that helper class, and published the service. The end result was that apart from the namespace and the service itself (because the code is meant to help create a relay out of any such WCF service), the code on my blog was identical to the code of a service running in production. Even though it did not reveal anything of my employer’s business, that’s not the point. The point is, since I used the code for a production service, it is not my intellectual property.

Maybe some time in future, time permitting, I can extend the class into a framework that handles other Http verbs, and serializes JSON as well as XML. And then I can republish it here. But until then, that is not code I can share.

Posted in General, Work | Leave a comment

The most annoying kind of error message?

What is the most annoying kind of error message, from a technical perspective? Or from the perspective of an error that was reported from an end user where you are a developer or in technical support?

I’ve changed my mind over the years. It used to be “the operation completed successfully” or “catastrophic failure” but nowadays I rate those as amusing.

There’s always the dreaded NullReferenceException:

3264.4

But as a programmer, I see that more as an embarrassing error. If someone reports one of these to the company I work for, it probably means a sloppy programming error. It also happens to be the easiest error to get, when one is being sloppy. So it can be annoying. But it isn’t the worst.

Actually I have two errors that annoy the shit out of me. I can’t say which is worse, so I’ll describe both…

The “error in someone else’s code incorrectly reported to us” error

Sometimes we receive error reports from a client liaison, someone who deals with clients who integrate with our software.

To give you a hypothetical example:

  1. I wrote a piece of software, a service that takes some info about doodads that people claim to own, and then queries the World Doodad Consortium to check if the doodads are valid and really belong to those people.
  2. People then use my service, by calling it programmatically inside their own software.
  3. Then tech support sends me an email that says the doodad service is not working. The email contains a screenshot of software I have never seen, which is displaying an error message, “the doodad service is offline”.
  4. Actually number three is the paraphrased version of the error message. To see it, one must scroll through thirty pages of email messages, several replies, angry rants demanding that this should be sorted out immediately, and so on, from three weeks ago… all of which only reach me now.

The kicker: My software never, under any circumstances, ever returns an error with the text “the doodad service is offline”. Hence I do not have any way of knowing what the error means.

It might mean:

  • The World Doodad Consortium returned invalid results because there was an issue there. My service reported this as per the software manual that their implementing developer should have read.
  • Everything is working perfectly but the integrator’s code failed to parse the response and their code fell into a catch-all that displayed an irrelevant general error, because their code assumes that if anything goes wrong in this block of code, it means the doodad service must have failed.
  • Their code never called us at all, but put the request into a queue to send later, and then because of some issue, maybe they set up a new client installation and didn’t configure it correctly, the queue never gets emptied.
  • In rare circumstances, perhaps 0.1% of all errors reported, the service genuinely failed, by returning something unexpected, which crashed the integrating client software.

Of course I have to take every error report seriously, and find out at the very least if:

  1. The request actually reached the service.
  2. If it did, whether or not we returned a valid response to the client.

Of course, more than nine times out of ten the request never reached the service, and the error actually means something went wrong in their software. And in the rare cases where the requests can be found on this end, more than nine times out of ten it turns out that we returned a perfectly valid response. Hence these errors are annoying.

Of course, there are some factors to keep in mind: The person who reported the error is a client liaison. His or her job is to keep the customer happy. He or she may not have enough context to determine the error is probably on the client system. Furthermore, the person who reported the error from the client side may be a manager, who also has no context on how or why the error happened. It would be extremely unprofessional not to give all those involved the benefit of doubt and treat their errors as important. Failing to attend to them and take them seriously may involve losing an important client.

The “system is behaving as designed” error

I’ll illustrate this kind of error with a hypothetical email… Note that the subject and message body contradict one another.

Subject: User Jack Ass can not access the High Security Risk page!!!

Message: I created user Jack Ass on the Doodad system, and gave him access to the High Security Risk page.

But Jack can access the High Security Risk Page! He should not be allowed to access that page! Please investigate and advise.

I don’t think this kind of error needs any further comment.

Posted in General, Programming, Work | Tagged | Leave a comment

My poor man’s factory pattern in c# – one way of instantiating all derived classes of an abstract base class

Apologies for deleting yesterday’s post. It used my actual code and the syntax highlighting was broken in my Open Live Writer. This one uses simplified code that I wrote in an example solution, and thus does not involve me sharing code that I do not own, and I managed to get the Open Live Writer plugin working again.

In a recent post I mentioned my “poor man’s factory pattern”. I’m sharing it here in case anyone needs something similar.

As I understand it, a factory pattern, typically implemented as either an abstract factory or a factory method, is a pattern where your classes decide which class to instantiate. So it’s a pattern all about instantiating objects, typically related objects that are all registered products used by association by some sort of owner, or factory.

Well, I wanted something similar, but also wanted to keep it simple.

I have a control that exposes a dialog, which is used by a user to capture data via a USB device. I have three different such devices, each with a completely different API, and so my project uses all of them via their SDK’s. You don’t need to know the details. The point is, a user might have any one of those three devices plugged in. My capture dialog needs to use whichever one is plugged in (or the first one that works if the user plugs more than one in, but they are not expected to do that).

So what I did was:

  1. Create a base class, an abstract class called Device.
  2. One of its properties is called DeviceFound, which returns a bool.
  3. Each derived type only sets that true if the device is plugged in and working. (Again, how they do that is implementation-specific details you don’t need to know.)

The control that uses the device contains a single property of the base type. So all that needs to happen, in the constructor of the control, is this:

  1. Iterate all the types in my assembly that are subtypes of Device, are not abstract, and have a parameterless public constructor.
  2. Create each one and check if DeviceFound is true.
  3. If that property is true, use that device; otherwise try the next one.
  4. For clarity, “use that device” means assign the working subtype found to the control’s single Device property, and use it when the user scans an image. If no working device is found, although that’s outside of the scope of this post, I simply display an error. (That code is not shown here.)

That is, I have three subtypes, but only want to use one of them; the first one I find to be valid. Unfortunately I must instantiate each in turn to check if it is the right one. It sounds expensive but in practice is really very fast.

Here is the code:

/* Get all subtypes of Device that are not abstract. This won't work with interfaces
 * but that's OK because we defined the base class as the Device abstract class. */
var scanners = from domainAssembly in AppDomain.CurrentDomain.GetAssemblies()
               from assemblyType in domainAssembly.GetTypes()
               where assemblyType.IsClass && !assemblyType.IsAbstract && assemblyType.IsSubclassOf(typeof(Device))
               select assemblyType;

foreach (Type type in scanners)
{
    try
    {
        var constructor = (type).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);
        if (constructor != null)
        {
            var scanner = constructor.Invoke(null) as Device;
            if (scanner.DeviceFound)
            {
                this.scanner = scanner;
                break;
            }
        }
    }
    catch
    {
        errorMessages.Add(string.Format("Unable to instantiate {0} scanner.", type.ToString()));
    }
}

The technique used here to find all subtypes of a type is based on the most popular answer on this StackOverflow question, except I optimized it somewhat to short circuit out anything found that isn’t a concrete class, and it uses Type.IsSubclassOf rather than Type.IsAssignableFrom, because I did not want the base class in my list, and it does not need to find interfaces.

If you want to do something similar, you can use IsAssignableFrom for interfaces instead. It could be further optimized to omit the check for a null constructor but I like it the way it is. I also need my control to behave and work even if, for example, two of the three device drivers have not been installed, in which case the code must still use the device that works (and swallow the exceptions for the other devices). Also, I do not take their last step and copy the enumeration to an array, for brevity. This is only an example. In production code, especially if your list of derived types is not going to change, you might want to cache the results in a List<Type> or array, and not enumerate every time your code is called.

And for interest sake, this is what my abstract class looks like:

using System.Collections.Generic;
using System.Linq;

namespace Example
{
    public abstract class Device
    {
        #region Properties

        public abstract bool DeviceFound { get; }

        public virtual IEnumerable<string> ErrorMessages
        {
            get
            {
                return Enumerable.Empty<string>();
            }
        }

        public abstract string Wsq64BitImage { get; }

        #endregion Properties

        #region Methods

        #region Public Methods

        public abstract bool ScanImage();

        #endregion Public Methods

        #endregion Methods
    }
}
Posted in Programming | Tagged , , | Leave a comment