Sunday, August 8, 2010

How to make a WPF Popup "Find control" - draggable


WPF – Windows Presentation Foundation offers a control called Popup which can be used to "Pop up" some texts or information whenever we need it. Also, this control can be customized so that it performs some specified function we need.

One instance when we can use Popup to create a custom control is when we have a requirement to create a "Find Control" box of our own. The reason behind using a Popup control rather than going for another window to design a Popup control is that, since the Popup is linked with that page where the information is shown (from which some text needs to be found out using the Find Control box), the communication between the Popup and the Window containing the content is easy.

So here we go...

First, create a UserControl to create a custom control for the TextArea where the text needs to be displayed...

Here is the xaml code:

   <Grid>
        <RichTextBox IsDocumentEnabled="True"
                     VerticalScrollBarVisibility="Visible"
                     HorizontalScrollBarVisibility="Visible" x:Name="uxRichTextBox" IsReadOnly="True"/>
    Grid>

Now write the code beside for this file.

public partial class CustomRichText : UserControl
    {
        private FindAndReplaceManager manager;
        public CustomRichText()
        {
            InitializeComponent();

            this.Loaded += delegate(object sender, RoutedEventArgs args)
            {
                if (!string.IsNullOrEmpty(FilePath))
                {
                    ProcessFile(FilePath,uxRichTextBox);
                }
            };
        }



        public string FilePath
        {
            get { return (string)GetValue(FilePathProperty); }
            set { SetValue(FilePathProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FilePath.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FilePathProperty =
            DependencyProperty.Register("FilePath", typeof(string), typeof(CustomRichText), new UIPropertyMetadata(new PropertyChangedCallback(FilePathChangeCallBack)));


        private static  void FilePathChangeCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            CustomRichText cRichTxtBx = d as CustomRichText;
            if (!String.IsNullOrEmpty(cRichTxtBx.FilePath))
            {
                ProcessFile(cRichTxtBx.FilePath, cRichTxtBx.uxRichTextBox);
            }
        }

        private static void ProcessFile(string filePath, RichTextBox richTextBox)
        {
            StreamReader file = new System.IO.StreamReader(filePath);
            richTextBox.Document = new FlowDocument();
            Paragraph p = new Paragraph();;
            while (!file.EndOfStream)
            {               
                p.Inlines.Add(file.ReadLine());
                p.Inlines.Add(new LineBreak());
            }
            richTextBox.Document.Blocks.Add(p);
        }              

        public void FindText(string searchText, FindOptions findOptions)
        {
            String findText = searchText;
            if (String.IsNullOrEmpty(findText))
            {
                return;
            }

            if (manager == null)
            {
                manager = new FindAndReplaceManager(uxRichTextBox.Document);
            }

            manager.CurrentPosition = uxRichTextBox.CaretPosition;

            TextRange textRange = manager.FindNext(findText, findOptions);
            if (textRange != null)
            {
                uxRichTextBox.Focus();
                uxRichTextBox.Selection.Select(textRange.Start, textRange.End);
            }
            else
            {
                if (manager.CurrentPosition.CompareTo(uxRichTextBox.Document.ContentEnd) == 0)
                {
                    MessageBox.Show("You've been reached to the end of the document!");
                    uxRichTextBox.CaretPosition = uxRichTextBox.Document.ContentStart;
                    manager.CurrentPosition = uxRichTextBox.CaretPosition;
                }
            }
        }      
    }

Now comes our original page where we are going to use this custom control.

The xaml goes like this...

<controls:CustomRichText FilePath="{Binding FilePath}"
                                 Grid.Row="2"
                                 x:Name="uxRichText" />
        <Popup x:Name="uxFind"
               AllowsTransparency="False"
               Width="363"
               Height="111"                                            
               StaysOpen="True" >
            <Border BorderThickness="2"
                    BorderBrush="#FFE8E6E2" BitmapEffect="{StaticResource popupShadow}">
                <Grid FocusManager.IsFocusScope="True"
                      HorizontalAlignment="Left"
                      Background="#FFD4D0C8"
                      Width="360"
                      Height="126"
                      >
                    <Grid.RowDefinitions>
                        <RowDefinition Height="21" />
                        <RowDefinition Height="30" />
                        <RowDefinition Height="30" />
                        <RowDefinition Height="30" />
                    Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="85" />
                    Grid.ColumnDefinitions>
                    <DockPanel Background="#FF274383"
                               Grid.Row="0"
                               Grid.Column="0"
                               Grid.ColumnSpan="2">
                        <TextBlock Text="Find"
                                   Height="16"
                                   Margin="2,2,0,2"
                                   Foreground="White"
                                   FontWeight="Bold" />
                        <Button x:Name="Close" Content="X"
                                Width="17"
                                Height="17.5"
                                HorizontalAlignment="Right" Click="Close_Click" />
                    DockPanel>
                    <StackPanel Orientation="Horizontal"
                                Grid.Column="0"
                                Grid.Row="1">
                        <TextBlock Text="Find what:"
                                   Margin="4,0,0,0"
                                   VerticalAlignment="Center" />
                        <TextBox  x:Name="uxSearchText"
                                  Width="192"
                                  Height="20"
                                  Margin="10,0,0,0" />
                    StackPanel>
                    <Button x:Name="Find" Content="Find Next"
                            Grid.Column="1"
                            Grid.Row="1"
                            Height="25"
                            Width="75" Click="Find_Click" />
                    <CheckBox x:Name="MatchCase" Content="Find match case only"
                              Grid.Row="2"
                              Margin="4,0,0,0" Click="FindOptions_Click"  />
                    <CheckBox x:Name="WholeWord" Content="Find whole word"
                              Grid.Row="3"
                              Margin="4,0,0,0" Click="FindOptions_Click" />
                    <Button x:Name="Cancel" Content="Cancel"
                            Grid.Row="2"
                            Grid.Column="1"
                            Height="25"
                            Width="75" Click="Close_Click" IsEnabled="{Binding ElementName=uxSearchText, Converter={StaticResource nullToVisibility}}" />
                Grid>
            Border>
        Popup>

In the code beside of this, we can set the "FilePath" property which is binded in the CustomRichText control to contain the full path of the file. You can define this property as a DependencyProperty like this

 public static readonly DependencyProperty FilePathProperty =
            DependencyProperty.Register("FilePath", typeof(string), typeof(FileViewer), new UIPropertyMetadata(string.Empty));

Now assume that we are showing this Find Control on the click of a Menu item called "Find". So inside the click event of the "Find" Menu item, add this code:

            uxFind.IsOpen = true;
         uxFind.StaysOpen = true;

Add the following code to Find a particular word and to close the Find control:-

  private void Close_Click(object sender, RoutedEventArgs e)
        {
            uxFind.IsOpen = false;
        }

        private void Find_Click(object sender, RoutedEventArgs e)
        {
            uxRichText.FindText(uxSearchText.Text, findOptions);
        }

        private void FindOptions_Click(object sender, RoutedEventArgs e)
        {
            if (WholeWord.IsChecked == true && MatchCase.IsChecked == true)
            {
                findOptions = FindOptions.MatchCaseAndWholeWord;
            }
            else if (WholeWord.IsChecked == false && MatchCase.IsChecked == true)
            {
                findOptions = FindOptions.MatchCase;
            }
            else if (WholeWord.IsChecked == true && MatchCase.IsChecked == true)
            {
                findOptions = FindOptions.MatchWholeWord;
            }
            else
            {
                findOptions = FindOptions.None;
            }
        }

Now everything is set and we have our find control in our hand. But this control has a problem. It is not draggable. The problem is that if we want to find a word, and if the word comes under this control, we cant move this to see the word. But dont worry, we have a solution for you...

The whole logic behind dragging a window lies in finding it’s initial position, find the initial mouse position by listening to MouseLeftButtonDown event. Also, find the last Mouse position by listening to the MouseLeftButtonUp event. Find the delta, add it to the initial TopLeft of the Find Control and there you can see your Find Control is re-located.

See the code for it below:-

First, add the following code to your “Find” menu item click event


MouseLeftButtonDown += new MouseButtonEventHandler(Window1_MouseLeftButtonDown);
            MouseLeftButtonUp += new MouseButtonEventHandler(Window1_MouseLeftButtonUp);
            s = new Size(363, 111);
            uxFind.Placement = PlacementMode.AbsolutePoint;
            uxFind.PlacementRectangle = new Rect(new Point(450, 450), s);
            initialPosOfPopup.X = Convert.ToInt32(uxFind.PlacementRectangle.Left);
            initialPosOfPopup.Y = Convert.ToInt32(uxFind.PlacementRectangle.Top);

Then write the handler methods:- for getting the first mouse position

      void Window1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
      {
          firstClickPoint = System.Windows.Forms.Cursor.Position;

      }

For getting the last mouse position, calculating delta, and applying it.

      void Window1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
      {
          lastClickPoint = System.Windows.Forms.Cursor.Position;
          int deltaX = lastClickPoint.X - firstClickPoint.X;
          int deltaY = lastClickPoint.Y - firstClickPoint.Y;

          if ((initialPosOfPopup.X < firstClickPoint.X) && (firstClickPoint.X < initialPosOfPopup.X + 200) && (initialPosOfPopup.Y < firstClickPoint.Y) && (firstClickPoint.Y < initialPosOfPopup.Y + 200))
              uxFind.PlacementRectangle = new Rect(new Point(Convert.ToDouble(deltaX) + initialPosOfPopup.X, Convert.ToDouble(deltaY) + initialPosOfPopup.Y), s);

          initialPosOfPopup.X = Convert.ToInt32(uxFind.PlacementRectangle.Left);
          initialPosOfPopup.Y = Convert.ToInt32(uxFind.PlacementRectangle.Top);
      }

Also, declare these variables as global:-

        System.Drawing.Point firstClickPoint;
        System.Drawing.Point lastClickPoint;
        System.Drawing.Point initialPosOfPopup;

        Size s;

Done!! Now you have a Find Control box which you can re-position.

Happy Coding…

2 comments:

  1. Nice code.

    Why can't you do a dragging handler for MouseMove instead of a MouseUp? That is what users would expect right?

    ReplyDelete