October 22, 2010

String Conversion C++ C#

C++/CLi to C#
std::string str = ...; 
System::String^ mgdstr = gcnew System::String(str.c_str();
and the other way
C# to C++
System::String^ value = ...;
CString cstr(value);
const std::string converted(cstr);

October 13, 2010

Static Property Reflection Sample

This sample enumerates over the "TargetClass" looking for all static properties that return the specified "ReturnType"
public static IEnumerable 
EnumerateReturnTypeProperties()
{
    Type type = typeof(TargetClass);
    PropertyInfo[] props = type.GetProperties();
    foreach (PropertyInfo prop in props)
    {
        // null as 1st GetValue parameter implies
        // only static properties enumerated
        ReturnType xxx = prop.GetValue(null, null)
            as ReturnType;
        if (xxx == null)
            continue;
        yield return xxx;
    }
}

Path.Combine

See here for a description of the behaviour of this method.
Following method resolves some weaknesses in the static method "Path.Combine" found in the System.IO namespace.
public static string PathCombine(string rootPath, string relativePath)
{
    Debug.Assert(rootPath != null);
    Debug.Assert(relativePath != null);

    string res = string.Empty;
    if (rootPath.EndsWith(":"))
    {
        rootPath += @"\";
    }
    // remove initial single '\' or '/' from relative path
    if (relativePath.StartsWith(@"\") || 
        relativePath.StartsWith(@"/"))
    {
        if (! (relativePath.StartsWith(@"\\") || 
               relativePath.StartsWith(@"//")))
        {
           relativePath = relativePath.Substring(1); 
        }
    }
    res = Path.Combine(rootPath, relativePath);
    res = res.Replace('/', '\\'); // replace '/' with '\'
    return res;
}

October 6, 2010

More Complex Progress Dialog

This sample does a file search asynchronously. At the end of the search the progress dialog pauses and closes itself. Internally a timer is used to initiate this automatic close of the progress dialog. AsynchFileFinder is the BackgroundWorker derived object. Here is th Xaml for the dialog:
<Window x:Class="FindingFilesDlg"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Finding modified files" Height="167" Width="514" Closing="Window_Closing" Loaded="Window_Loaded">
    <Grid>
        <Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,12" Name="butCancel" VerticalAlignment="Bottom" Width="91" Click="butCancelDone_Click">_Cancel</Button>
        <TextBlock Height="21" Margin="12,15,0,0" Name="tbActivity" VerticalAlignment="Top"  Text="Searching for modified files:" HorizontalAlignment="Left" Width="Auto" />
        <ProgressBar Margin="12,61,0,52" Name="progressBar" HorizontalAlignment="Left" Width="427" />
        <TextBlock Margin="0,61,16,52" Name="tbPercentage" Text="0%" HorizontalAlignment="Right" Width="31" TextAlignment="Right" />
    </Grid>
</Window>
Here is the code for the dialog:
public partial class FindingFilesDlg : Window
{
    AsyncFileFinder m_AsyncFileFinder = null;
    DispatcherTimer m_Timer = new DispatcherTimer();
    IEnumerable<FileSystemInfo> m_FilesFound = 
               new FileSystemInfo[0];
    int countDown = 1;
    const int SECOND = 10000000;
    bool m_Completed = false;

    public FindingFilesDlg(string rootDir, FileSearchRules fsr)
    {
        InitializeComponent();
        tbActivity.Text = "Preparing to search \'" +
            rootDir + "\' for modified files:";

        InitialiseTimer();
        InitialiseAsyncFileFinder(rootDir, fsr);
    }

    private void InitialiseAsyncFileFinder(
        string rootDir, 
        FileSearchRules fsr)
    {
        // The BackgroundWorker derived object
        m_AsyncFileFinder = new AsyncFileFinder(rootDir, fsr);
        m_AsyncFileFinder.ProgressChanged +=
            new ProgressChangedEventHandler(
                AsynchFileFinder_ProgressChanged);
        m_AsyncFileFinder.RunWorkerCompleted += new
            RunWorkerCompletedEventHandler(
            AsynchFileFinder_RunWorkerCompleted);
    }

    private void InitialiseTimer()
    {
        // Disable timer
        m_Timer.IsEnabled = false;
        // Set timer event interval
        m_Timer.Interval = new TimeSpan((long)(1 * SECOND));
        // Timer events
        m_Timer.Tick += new EventHandler(timer_Tick);
    }

    public IEnumerable<FileSystemInfo> FilesFound
    {
        get { return m_FilesFound; }
    }

    void AsynchFileFinder_RunWorkerCompleted(
        object sender, 
        RunWorkerCompletedEventArgs e)
    {
        if (!e.Cancelled)
        {
            m_Completed = true;
            m_FilesFound = m_AsyncFileFinder.Files;
            tbActivity.Text = "Finished searching \'" + 
                m_AsyncFileFinder.SearchDirectory + 
                "\', " + m_FilesFound.Count().ToString() + 
                " files found";
            progressBar.Value = progressBar.Maximum;
            tbPercentage.Text = "100%";
            UpdateDoneButton();
            // wait several seconds before closing the dialog
            m_Timer.Start(); 
        }
    }

    void UpdateDoneButton()
    {
        string str = "_Done (" + 
              countDown.ToString() + ")";
        butCancel.Content = str;
    }
    
    bool textUpdated = false;
    void AsynchFileFinder_ProgressChanged(object sender, 
        ProgressChangedEventArgs e)
    {
        Debug.Assert((e.ProgressPercentage >= 0) && 
            (e.ProgressPercentage <= 100));
        progressBar.Value = e.ProgressPercentage;
        tbPercentage.Text = e.ProgressPercentage.ToString() +
                "%";
        if ((e.ProgressPercentage > 1) && !textUpdated)
        {
            textUpdated = true;
            tbActivity.Text = "Searching \'" + 
                m_AsyncFileFinder.SearchDirectory + 
                "\' for modified files (" + 
                m_AsyncFileFinder.NumFilesToSearch.ToString() + 
                " files to search) :";
        }
    }

    private void Window_Closing(object sender, CancelEventArgs e)
    {
        CancelAsyncFileFinder();
        if (m_Timer.IsEnabled)
            StopTimer();
        m_AsyncFileFinder.Dispose();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        m_AsyncFileFinder.RunWorkerAsync();
        tbActivity.Text = "Searching \'" + 
            m_AsyncFileFinder.SearchDirectory + 
            "\' for modified files:";
    }

    // wait several seconds before closing the dialog
    void timer_Tick(object sender, EventArgs e)
    {
        --countDown;
        UpdateDoneButton();
        if (countDown == 0)
        {
            StopTimer();
            DialogResult = m_Completed;
        }
    }

    private void StopTimer()
    {
        m_Timer.Stop();
    }

    private void butCancelDone_Click(object sender, 
          RoutedEventArgs e)
    {
        if (!m_Completed)
        {
            CancelAsyncFileFinder();
            if (m_Timer.IsEnabled)
                StopTimer();
        }
        DialogResult = m_Completed;
    }

    private void CancelAsyncFileFinder()
    {
        if (m_AsyncFileFinder.IsBusy)
            m_AsyncFileFinder.CancelAsync();
    }
}

Here is the code for the background worker object:
internal class AsyncFileFinder : BackgroundWorker
{
    private FileSearchRules FileSearchRules { get; set; }
    string m_RootDir = string.Empty;
    List<System.IO.FileSystemInfo> files = new 
        List<System.IO.FileSystemInfo>();
    int m_NumberFilesToSearch = 0;

    // Pass required info in through constructor, 
    // cant change it whilst a search is underway
    public AsyncFileFinder(string rootDir, 
        FileSearchRules fsr) : base()
    {
        this.WorkerReportsProgress = true;
        this.WorkerSupportsCancellation = true;
        m_RootDir = rootDir;
        FileSearchRules = new FileSearchRules(fsr);
    }

    public string SearchDirectory
    {
        get { return m_RootDir; }
    }

    public IEnumerable<FileSystemInfo> Files
    {
        get
        {
            if (IsBusy) // No access while thread active
                return new List<FileSystemInfo>();
            else
                return files.AsReadOnly();
        }
    }

    // Total number of files that will be checked
    public int NumFilesToSearch
    {
        get
        {
            return m_NumberFilesToSearch;
        }
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        bool cancelled = false;
        double progress = 0;
        double prevProgress = 0;
        files = new List<FileSystemInfo>();
        DirectoryInfo targ = new DirectoryInfo(m_RootDir);
        // Count the number of files to process first
        foreach (System.IO.FileSystemInfo fsi in
            targ.IterateFiles(true))
        {
            if (CancellationPending)
            {
                cancelled = true;
                break;
            }
            
            Interlocked.Increment(ref 
                m_NumberFilesToSearch);
        }
        int count = 0;
        if (!cancelled)
        {
            foreach (FileSystemInfo fsi in 
                targ.IterateFiles(true))
            {
                if (CancellationPending)
                {
                    cancelled = true;
                    break;
                }

                ++count;
                // progress as a percentage
                progress = (100.0 * (double)count /
                    (double)m_NumberFilesToSearch) + 0.5;
                // Report every 1% of progress
                if ((progress - prevProgress) > 1.0)
                {
                    ReportProgress((int)(progress));
                    prevProgress = progress;
                }

                if (FileSearchRules.PassesRules(fsi))
                {
                    files.Add(fsi);
                }
            }
        }
        e.Cancel = cancelled;
    }
 
}

General Purpose Progess Dialog

This is a general purpose progress dialog. It takes a background worker object as a parameter. Here is the Xaml for the dialog:
<Window x:Class="AsyncMessageDlg"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="AsyncMessageDlg" Height="314" Width="750"
         WindowStartupLocation="CenterOwner" 
         Closing="Window_Closing" Loaded="Window_Loaded">
    <Grid>
        <Ellipse Fill="Azure" Height="56" Margin="0,27,120,0" Name="ellipse2" 
        Stroke="Beige" VerticalAlignment="Top" HorizontalAlignment="Right" Width="82" />
        <Ellipse Fill="Azure" Height="56" Margin="120,0,0,20" Name="ellipse1" 
        Stroke="Beige" VerticalAlignment="Bottom" HorizontalAlignment="Left" Width="82" />
        <TextBox Margin="12,35,12,63" Name="textBox" IsReadOnly="True" 
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" />
        <Button Height="23" Margin="560,0,0,6" VerticalAlignment="Bottom" 
Name="butOK" HorizontalAlignment="Left" Width="75" IsDefault="True" Click="butOK_Click">_Ok</Button>
        <Button Height="23" HorizontalAlignment="Right" Margin="0,0,12,6" 
Name="butCancel" VerticalAlignment="Bottom" Width="75" IsCancel="True" Click="butCancel_Click">_Cancel</Button>
        <Label Height="28" Margin="12,7,12,0" Name="label" 
VerticalAlignment="Top">Label</Label>
        <ProgressBar Height="16" Margin="120,0,181,41" Name="progressBar" 
VerticalAlignment="Bottom" Visibility="Hidden" />
        <TextBlock Height="16" HorizontalAlignment="Right" Margin="0,0,144,41" 
Name="tbProgressPercent" Text="0%" TextAlignment="Right" VerticalAlignment="Bottom" Width="31" Visibility="Hidden" />
    </Grid>
</Window>
The dialog takes a string for the dialog title and a string for the label above the output text window. When the background worker task sends a progress update it tries to convert the UserState object passed in the event to a string which is added to the output window. Here is the code for the dialog:
public partial class AsyncMessageDlg : Window
{
    BackgroundWorker m_WorkerThread = null;
    bool m_Completed = false;

    public AsyncMessageDlg(
        string textBoxTitle,
        string dlgTitle, 
        BackgroundWorker bw)
    {
        Debug.Assert(bw != null);
        InitializeComponent();
        this.Title = dlgTitle;
        this.label.Content = textBoxTitle;
        m_Completed = false;
        InitialiseBackgrounWorker(bw);
    }

    private void InitialiseBackgrounWorker(BackgroundWorker bw)
    {
        Debug.Assert(bw != null);
        Debug.Assert(bw.WorkerSupportsCancellation);
        this.m_WorkerThread = bw;
        //if (m_WorkerThread.WorkerReportsProgress)
        butCancel.IsEnabled = m_WorkerThread.WorkerSupportsCancellation;
        butOK.IsEnabled = false;
        tbProgressPercent.Visibility = Visibility.Visible;
        progressBar.Visibility = Visibility.Visible;

        m_WorkerThread.ProgressChanged += new 
            ProgressChangedEventHandler(
              m_WorkerThread_ProgressChanged);
        m_WorkerThread.RunWorkerCompleted += new 
            RunWorkerCompletedEventHandler(
              m_WorkerThread_RunWorkerCompleted);
    }

    void m_WorkerThread_RunWorkerCompleted(object sender, 
        RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled)
            return;
        m_Completed = true;
        string result = e.Result as string;
        if (result != null)
        {
            label.Content = result as string;
        }
        progressBar.Value = progressBar.Maximum;
        tbProgressPercent.Text = "100%";
        butOK.IsEnabled = true;
        butCancel.IsEnabled = false;
    }

    void m_WorkerThread_ProgressChanged(object sender, 
        ProgressChangedEventArgs e)
    {
        Debug.Assert((e.ProgressPercentage >= 0) &&
            (e.ProgressPercentage <= 100));
        progressBar.Value = e.ProgressPercentage;
        tbProgressPercent.Text = e.ProgressPercentage.ToString() +
            "%";
        if (e.UserState != null) 
        {
            if (e.UserState is string)
            {
                textBox.Text += e.UserState as string;
            }
            else
            {
                textBox.Text += e.UserState.ToString();
            }
        }
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        m_WorkerThread.RunWorkerAsync();
    }

    private void Window_Closing(object sender, CancelEventArgs e)
    {
        CancelWorkerThread();
        m_WorkerThread.Dispose();
    }

    private void butOK_Click(object sender, RoutedEventArgs e)
    {
        DialogResult = m_Completed;
        this.Close();
    }

    private void butCancel_Click(object sender, RoutedEventArgs e)
    {
        if (!m_Completed)
        {
            CancelWorkerThread();
        }
        DialogResult = m_Completed;
    }

    private void CancelWorkerThread()
    {
        if (m_WorkerThread.IsBusy && 
            m_WorkerThread.WorkerSupportsCancellation)
            m_WorkerThread.CancelAsync();
    }
}
Here is the code for a sample background worker
// Background worker to check out some files
internal class AsynchAddCheckOutFiles : BackgroundWorker
{
    List<FileSystemInfo> m_fileSystemInfoList;

    // Pass required info in through constructor, cant change it
    // whilst a search is underway
    public AsynchAddCheckOutFiles(ref List<FileSystemInfo> fileSystemInfoList) :
        base()
    {
        this.m_fileSystemInfoList = fileSystemInfoList;
        // Set clients refernce to null, we own the list.
        fileSystemInfoList = null; 
        this.WorkerReportsProgress = true;
        this.WorkerSupportsCancellation = true;
    }

    protected override void OnDoWork(DoWorkEventArgs e)
    {
        bool cancelled = false;
        double progress = 0;
        double prevProgress = 0;

        int m_NumberFiles = m_fileSystemInfoList.Count;
        int count = 0;
        if (!cancelled)
        {
            foreach (FileSystemInfo fsi in m_fileSystemInfoList)
            {
                if (CancellationPending)
                {
                    cancelled = true;
                    break;
                }

                Perforce.P4CommandTransaction res = 
                    Perforce.Instance.AddCheckOutFile(fsi);
                string state = res.ToString();

                ++count;
                // progress as a percentage
                progress = (100.0 * (double)count /
                    (double)m_NumberFiles) + 0.5;
                // Report every 1% of progress
                if ((progress - prevProgress) > 1.0)
                {
                    ReportProgress((int)(progress), state);
                    prevProgress = progress;
                }
                
            }
        }
        e.Cancel = cancelled;
    }

}