Jaime Rodriguez
On Windows Store apps, Windows Phone, HTML and XAML

  • Jaime Rodriguez

    VS2008 Cider beta2 .. feedback wanted ..

    • 0 Comments

    Today I spent two hours in Cider beta2..
    I was not sure what to expect, I must admit in the past I had just disabled it cause I did not enjoy waiting for the "whoops" page..

    Here is my first [raw,unedited, un-sugar-coded] impression...

    • The parser definitely surprised me..  A lot of my customer projects from the Vista launch opened in beta2... [a few that did not need DesignTime tweaks, same than they did in Blend]
    • I enjoyed the intellisense against my own controls...  (as opposed to old schema/hacky based intellisense)..
    • I was not as impressed with the perf...  Our team will have to improve on that (don't worry, they are on it)
    • Overall the drag & drop into design surface felt good.. much more useful than in beta1... but I have been doing so much Blend over past year that VS felt it could use some small polishing...  that one you will have to submit as feedback, they won't take my word for it, they know how I dress :( 

    So why the post if it is not all perfect?? Because the time for feedback is now!!   If you have projects that the parser can't handle, let the cider team know ..    Here is the proposal/offer ...   
    If for what ever reason you don't want to download the VPC or the VS2008 DVD, drop me an email w/ your shipping address and I will ship you a DVD with a VPC -- offer limited to first 50 projects, maybe more pending your patience and your project size --...  

    I will come back to Cider with a more detailed review next week...  but wanted to encourage you all to try it and provide the feedback...

     

     

     

     

     

     

     

  • Jaime Rodriguez

    VS2008 (Orcas) beta2 ...

    • 0 Comments

    I often skip the "xyz" has shipped announcements.. but VS2008 "orcas" beta2  is packed with so much goodness that I had to jump in to vouch for it..   

    Check out Soma/ScottGu's video on beta2 http://channel9.msdn.com/showpost.aspx?postid=329443... 

    Start downloading beta2 while you watch the video, cause by the end no chance you will not be interested in playing with it..

    I am getting late into it cause I was on vacation last two weeks but here is what I see so far:

    • The uninstall of beta1 and install of beta2 was surprisingly smooth for me.. that is a first..
    • I am quite interested in testing -- and hearing feedback from customers - on the WPF progress..  ( animation improvements & the features to host add-ins, in particular)..  You can read about some of the WPF improvements from tim's blog or the WPF SDK team's blog..
    • On Web development, ScottGu has the skinny on this post..
      • My experiences:
        •  I have been enjoying the much improved Javascript debugging since beta1 ...
        • I have also been using Expression Web due to the imho much better CSS support..  so it will be nice to see that goodness inside VS..
    • Today I spent two hours inside Cider beta2..   You can read about the beta2 Cider features from their wiki..  Immediately after this post, I will share some of my experience/expectations..  Sorry for 'dupe' post.. but I am guessing half of you won't read this far into  a VS2008 post...  

    Still here? What r u waiting for.. Please go download it and have lots of fun... do file bugs when you find them..

  • Jaime Rodriguez

    Drag & Drop in WPF.. part 3 .. the results and code...

    • 3 Comments

    Here is where we take every thing learned in Part1 (the drag) and Part2  (the drop)  and package it into more realistic scenarios...  

    Let's first begin by walking through a few items we skipped in earlier parts for brevity:

    Detecting DragDropEffects...

    When you start a drag, you should check if Ctrl  or Shift  are pressed...    Each one of these being pressed implies a different action for our drag operation...

     DragDropEffects effects = DragDropEffects.None;
    bool ctrl = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
    bool shift = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
    
    if (ctrl && shift && AllowsLink)
            effects |= DragDropEffects.Link;
    else if (ctrl && AllowsCopy)
           effects |= DragDropEffects.Copy;
    else if (AllowsMove)  // the default is to move in my sample .. 
           effects |= DragDropEffects.Move;

     

    Detecting what needs to be dragged:

    Deciding what needs to be dragged is sucky because you almost always have to put app knowledge into your drag & drop code..   There is not a one size fits all..

    For example:  <Button Content="Test" />    will generate a tree that looks like:     Button - > Chrome - > Content Presenter - > TextBlock   ...
    So, if you hitTest on what is visible, you might get the  TextBlock  when what you wanted was the button ....


    I don't have a great answer for this. As I mentioned, my approach is to lazily shoot for the OriginalSource element in the MouseMove event handler..   This seems to work slightly better than doing HitTesting (where I could get an item lower in the tree like the TextBlock ..

    For those that do want to do hittesting, the code is trivial:

     UIElement GetDragElementFromHitTest(UIElement dragSourceContainer, MouseEventArgs args)
            {
                HitTestResult hr = VisualTreeHelper.HitTest( dragSourceContainer, args.GetPosition((IInputElement) dragSourceContainer));
                return hr.VisualHit as UIElement;
            }

     

    Doing Drag & drop should be about data:

    I have to emphasize d&d was meant for data..   In the demo below I make the mistake of letting you drag UIElements and the like.. This is for illustration purposes... imho if you find yourself trying to drag any thing other than data, you should evaluate decoupling presentation and data.. 

     

    Now onto a real sample...  

    What I have done for my demos is create a library with:

    • DragHelper class:  given a source  (any UIElement) will subscribe to handle the required events to initiate a Drag& Drop operation.
    • DropHelper class: given a target ( any UIElement) will set properties needed  & subscribe to required events to complete a Drop operation...
    • DragAdorner -- same as previous examples, the class to draw the Adorner while element drags..

     

    I have also defined an interface that a class can implement to aid in the dragOperation...  

        public interface IDataDropObjectProvider
        {
            //Flag of actions sypported by implementation of  IDataDropObjectProvider
            DragDropProviderActions SupportedActions { get; }
    
            // Called before StartDrag () to get the Data () to be used in the DataObject 
            object GetData(MouseEventArgs e);
            // Called to get the visual ( UIElement visual brush of the object being dragged.. 
            UIElement GetVisual(MouseEventArgs e);
            // Gives feedback during Drag 
            void GiveFeedback(GiveFeedbackEventArgs args);
            // implements ContinueDrag -- to canceld the D&D.. 
            void ContinueDrag(QueryContinueDragEventArgs args);
            // called by the TARGET object .. this will attempt to "unparent" the current child so we can add it a child some where else.. 
            bool UnParent();
    
        } 
    This interface gets called back from either DragHelper or Drophelper  (whenever needed)
    A few things to notice: 
    • the interface is not necessarily implemented by either the source or the target. it is de-coupled from both.  Any other class can implement it and it can be generic. 
    • The interface is optional, if you do not implement it, the helpers classes will do their best to help..  My advise is always implement the GetData ( ) part of the interface. 
    • You can implement just part of the interface.  DragDropProviderActions is a flag that says what actions the interface supports.. 
     

    Wiring the 'helpers'

    Is pretty trivial ...   the constructor for DragHelper looks like this:

      public DragHelper(UIElement source, IDataDropObjectProvider callback, UIElement dragScope)
       // source is the drag source control ... 
       // callback is the interface  optional, pass null if you don't want to implement for call backs to GetData () , GetVisual(), etc.
      /*  dragScope is the hack we use if we are going to use DragOver...    Even though DragOver is a drop target event, we need to know about it in the drag ... because we have to wire up for it before calling DoDragDrop () ....   */

     

    Here are examples of using the constructors...    ( DropHelper I did not explain above beccuse the only parameter in is the drop target control itself..

    dragHelper = new DragHelper(this.canvas, null, null);
    dropHelper = new DropHelper(this.dropTargetPanel1 );
    
    dragHelperWScope = new DragHelper(this.canvas1,  null, (UIElement)this.BorderForScope);
    dropHelperWScope = new DropHelper(this.dropTargetPanel2);

    For the most part, these classes do what we discussed .. the exception is the drop..

    Common drop issues:

    Drop of data is straight forward...   there is no generic recipe for all scenarios but two that I use often are:  dropping to ContentControl or dropping to ItemsControl..  in which case I set appropriate property or add the data to the items collection ..

    Drop of UIElements is slightly trickier mostly because you need to re-parent your element to the target.. .  The sample classes use the IDataDropObjectProvider to Unparent () the UIElement before dropping it..

    Note that this assumes every thing is in process, the interface passes UIElements and other stuff around ...     To implement an Out of process handler for passing UI, you can make COM objects and pass that around..

     

    The sample

    Is not pretty ...  but quite functional. .. Here is the first half ..  

    image The app shows the two approaches:

    The Top grid 
    allows you to drag the rectangle and button any where you want...
    If you drag in the app to the TargetPanel, it will drag as UIElement
    If you drag in the app to the RichTextBox, it will drag as Data (Text, hardcoded "abcd" )..
    You can also use that to drag out of process ( Word is that I used)...
    You can drag Text from word into the RichTextbox  (duh ) and into the Target Panel ( Creates a TextBlock for you ) ...

    The bottom grid, does the same than top, but won't let you drag out of the Red border's scope...  It how ever does not limit the drop, so you can drop from any where ( word, or top grid)...

     

    The second half of the app is even uglier, it is in Window2.xaml  but it is a common scenario...

    image

    This does drag & drop across listboxes..
    it uses Data, but provides a Visual Adorner for feedback...

    The things to notice is that it implements the IDataDropObjectProvider interface ... so you can see how that works..

    You can click one of the planets and drag into Word or other external app that allows for text drops to get the XML representation of the planet..

    The listbox that you are dropping into does not allow duplicates.. so don't try dropping same thing twice..  it will quietly smirk at you..

     

    OK that covers my D&D walk through ...  The source for the sample in part 3 is here..

    {let me know if you find bugs or issues }

  • Jaime Rodriguez

    Drag &amp; drop in WPF part2 ...

    • 0 Comments

    Last part, we focused on the drag...   In this part we will focus on drop (which is relatively straight forward) ...  

    DropTargets must set  AllowDrop = true

    It all begins with a Visual Element setting its AllowDrop to true; this tells the D&D subsystem your control is a drop target  ...  You can do this in XAML or in code.. 

     

    DragEnter, DragOver, DragLeave

    I hardly ever subscribe to any of these events..  but let's walk through the niceties on each ..

    • DragEnter -- happens when cursor/mouse enters;  useful for things like "highlighting" the target by showing an adorner, or similar ...     You would think that when mouseEnter, you can query the dataobject and provide feedback on whether the drop would be allowed... Well, you can, but it is pretty useless. Dragover can override that, so the place to handle the feedback is on DragOver..
    • DragLeave -- happens when cursor leaves..  Here you should undo any thing you did on DragEnter ..
    • Dragover -- here you can provide feedback to whether the drop would succeed .. 

    Walking through the Drop code with an example:

    From our previous work in progress project, I replaced old DropTarget StackPanel with a DropTargetText and DropTargetImages ...  
    DropTargetImages will handle DragOver,DragEnter, Dragleave  to check if dataObject type is Images and if not, it will set e.Effects to None, and provide some ugly feedback..
    DropTargetText will handle Dragover  and Drop  (since our old dataobject type is Text)

    void Window1_Loaded(object sender, RoutedEventArgs e)
         {
             this.DragSource.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(DragSource_PreviewMouseLeftButtonDown);
             this.DragSource.PreviewMouseMove += new MouseEventHandler(DragSource_PreviewMouseMove);

             // DROP
             this.DropTargetImages.DragEnter += new DragEventHandler(DropTargetImages_DragEnter);
             this.DropTargetImages.DragLeave += new DragEventHandler(DropTargetImages_DragLeave);
             this.DropTargetImages.DragOver += new DragEventHandler(DropTargetImages_DragOver);
             this.DropTargetText.DragOver += new DragEventHandler(DropTargetText_DragOver);
             this.DropTargetText.Drop += new DragEventHandler(DropTargetText_Drop);

         }

         void DropTargetText_Drop(object sender, DragEventArgs e)
         {
             IDataObject data = e.Data;

             if (data.GetDataPresent(DataFormats.Text))
             {
                 MessageBox.Show(
                     string.Format("right format, thanks for dropping '{0}'" ,
                     ((string)data.GetData(DataFormats.Text))));
             }

         }

         void DropTargetImages_DragOver(object sender, DragEventArgs e)
         {
             if (!e.Data.GetDataPresent("Images"))
             {
                 e.Effects = DragDropEffects.None;
                 e.Handled = true;
             }
         }

         void DropTargetText_DragOver(object sender, DragEventArgs e)
         {
             if (!e.Data.GetDataPresent("Text"))
             {

                 e.Effects = DragDropEffects.None;
                 e.Handled = true;
             }
         }

         void DropTargetImages_DragLeave(object sender, DragEventArgs e)
         {
             RunStoryboard("Timeline2");

         }

         void RunStoryboard(string name)
         {
             Storyboard sb = this.FindResource(name) as Storyboard ;
             System.Diagnostics.Debug.Assert(sb != null);
             sb.Begin(DropTargetImages);

         }

         void DropTargetImages_DragEnter(object sender, DragEventArgs e)
         {
             if (!e.Data.GetDataPresent("Image"))
             {
                 RunStoryboard("Timeline1");

                 e.Effects = DragDropEffects.None;
                 e.Handled = true;
             }

         }

     

    The source code for snippet above is here.

    Handling the Drop can be that straight forward,  but it almost never is that simple.. 

    In Part3, I can walk through much more samples of handling drop ... 

  • Jaime Rodriguez

    Drag &amp; Drop in WPF ... Explained end to end ..

    • 6 Comments

    How to do Drag& Drop in WPF is a question I hear often... 
    I have seen some great samples out there, but most focus on either a big code sample or a niche scenario...  a couple of times I have ended up having to help some one who got stuck.
    I hope the below write up is useful to explain the steps and decisions to get drag & drop done.. and it comes with sample snippets ...

    -- ---- -------------------
    From  [http://msdn2.microsoft.com/en-us/library/aa289508(vs.71).aspx]  Here is the sequence of events in a typical drag-and-drop operation:

    1. Dragging is initiated by calling the DoDragDrop method for the source control.

      The DoDragDrop method takes two parameters:

      • data, specifying the data to pass
      • allowedEffects, specifying which operations (copying and/or moving) are allowed

      A new DataObject object is automatically created.

    2. This in turn raises the GiveFeedback event. In most cases you do not need to worry about the GiveFeedback event, but if you wanted to display a custom mouse pointer during the drag, this is where you would add your code.
    3. Any control with its AllowDrop property set to True is a potential drop target. The AllowDrop property can be set in the Properties window at design time, or programmatically in the Form_Load event.
    4. As the mouse passes over each control, the DragEnter event for that control is raised. The GetDataPresent method is used to make sure that the format of the data is appropriate to the target control, and the Effect property is used to display the appropriate mouse pointer.
    5. If the user releases the mouse button over a valid drop target, the DragDrop event is raised. Code in the DragDrop event handler extracts the data from the DataObject object and displays it in the target control.

    ------------

    Let's walk through it in WPF...

    Detecting Drag & Drop.

    Before the DoDragDrop is called, we must detect a mouse Drag operation on the source...  A mouse drag is usually a MouseLeftButtonDown + a MouseMove (before MouseLeftButton goes up) ...

    So, our drag & drop source control needs to subscribe to these two events:

    void Window1_Loaded(object sender, RoutedEventArgs e)
         {
             this.DragSource.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(DragSource_PreviewMouseLeftButtonDown);
             this.DragSource.PreviewMouseMove += new MouseEventHandler(DragSource_PreviewMouseMove);
         }

    To prevent from starting a false drag & drop operation where the user accidentally drags, you can use SystemParameters.MinimumHorizontalDragDistance  and SystemParameters.MinimumVerticalDragDistance

    One way to do this is on MouseLeftButtonDown, record the starting position  and  onMouseMove check if the mouse has moved far enough..

            void DragSource_PreviewMouseMove(object sender, MouseEventArgs e)
            {
                if (e.LeftButton == MouseButtonState.Pressed && !IsDragging)
                {
                    Point position = e.GetPosition(null);
    
                    if (Math.Abs(position.X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
                        Math.Abs(position.Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
                    {
                      StartDrag(e); 
    
                    }
                }   
            }
    
            void DragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                _startPoint = e.GetPosition(null);
            }
    
     

     

    Its a Drag .. now what?

    The data!  You need to find out what is under the mouse when dragging.
    I will omit take the easy way out and assume that whoever is triggering the MouseMove is what I want to drag .. so look at MouseEventArgs.OriginalSource..   [or you could do some 2D HitTesting using VisualTreeHelper .. In Part3 of this write up will try to walk you through hit testing the listbox -which is the other common scenario I encounter-.

    Once you have the object to drag, you will need to package what you are a sending into a DataObject that describes the data you are passing around. 
    DataObject is a wrapper to push generic data (identified with extensible formats) into drag/drop..  As long as both the source and destination understand the format, you will be set.  As such, DataObject has a couple interesting methods:

    • SetData (  Type format, object data )    /// format is the "format" of the day you are passing ( e.g. Formats.Text,  Formats.Image, etc.. ) you can pass any custom types.
    • GetDataPresent (  Type format )  /// is what the drop target will use to inquire and extract the data .. if it is a type it can handle, it will call GetData () and handle it ..

    Not much interesting stuff here..  In the sample I just hard-coded my data to be of type string... this makes it easier to paste into external containers (for example Word, which you can use to test this part of the write-up).   I do have to stress that drag & dropping should be about the data ... 

    Providing visual feedback during the drag & drop operation..

    Before we call DoDragDrop () we have a few 'choices' to make around the feedback we want to provide and the 'scope' of the d&d.  

    • Do we want a custom cursor to display while we are doing the Drag operation ?  If we want a cursor, what should it be??
    • How far do we want to drag?? within the app or across windows apps?

     

    Simplest scenario:  No custom cursor and we want it to drag across apps: 

    If you don't want a fancy cursor, you are done!!  You can call DoDragDrop directly ...

     private void StartDrag(MouseEventArgs e)
            {
                IsDragging = true;
                DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
                DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
                IsDragging = false;
            }

    Note: this code allows you to drag & drop across processes, it uses the default operating system feedback ( e.g. + for copy).. 

     

    Next scenario: We want a pre-defined custom cursor...    

    Say we had a .cur file and embedded it on to our application as a resource ( see sample code).   We can subscribe to GiveFeedback () and wire our cursor there..

    private void StartDragCustomCursor(MouseEventArgs e)
            {
    
                GiveFeedbackEventHandler handler = new GiveFeedbackEventHandler(DragSource_GiveFeedback);
                this.DragSource.GiveFeedback += handler; 
                IsDragging = true;
                DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
                DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
                this.DragSource.GiveFeedback -= handler; 
                IsDragging = false;
            }

    Our handler for feedback looks like this:

    void DragSource_GiveFeedback(object sender, GiveFeedbackEventArgs e)
            {
                    try
                    {
                        //This loads the cursor from a stream .. 
                        if (_allOpsCursor == null)
                        {
                            using (Stream cursorStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(
    "SimplestDragDrop.DDIcon.cur")) { _allOpsCursor = new Cursor(cursorStream); } } Mouse.SetCursor(_allOpsCursor); e.UseDefaultCursors = false; e.Handled = true; } finally { } }

    Two things to notice: 
    1) I cached the cursor...  GiveFeedback will be called many times  as the mousemoves so I cached it..  and

    2) though I did not handle it, I called it "_allOpsCursor" because GiveFeedbackEventArgs will tell you the possible operation for the cursor (e.Effects)...  I could have used multiple cursors, one for each effect.

     

    Next scenario: Getting fancy and using the Visual we are dragging for feedback [instead of a cursor]

    The first thing you will need is to an Adorner; in my case I chose and adorner that contains a VisualBrush of the Element being dragged...      you can go with RenderTargetBitmap, or possibly reparent the object directly ... but I like VisualBrush in case the drag is cancelled..  

    The constructor for the adorner class is where most of the action happens:

    public DragAdorner(UIElement owner, UIElement adornElement, bool useVisualBrush, double opacity)
                : base(owner)
            {
                _owner = owner;
                if (useVisualBrush)
                {
                    VisualBrush _brush = new VisualBrush(adornElement);
                    _brush.Opacity = opacity;
                    Rectangle r = new Rectangle();
                    r.RadiusX = 3;
                    r.RadiusY = 3;
                    r.Width = adornElement.DesiredSize.Width;
                    r.Height = adornElement.DesiredSize.Height;
    
                    XCenter = adornElement.DesiredSize.Width / 2;
                    YCenter = adornElement.DesiredSize.Height / 2;
    
                    r.Fill = _brush;
                    _child = r;
    
                }
                else
                    _child = adornElement;
    
            }

     //There is more code in DragAdorner, but mostly used for positioning the adorner as the drag is happening... please refer to the sample...

     

    Now, that we have our custom adorner ready, the tricky part is wiring it so it follows the cursor position.  There are two options here:

    • If we want drag & drop across apps, we are going to have to call Win32's GetCursorPos () ...   This is trivial to write but requires full-trust ...  (which you likely had if you needed to drag & drop with other apps anyway )...
    • If we want to drag & drop inside our app only or inside a specific 'scope' with in the app, there is a hucky workaround that I often use to avoid the interop code..

     

    Using Visual for Feedback.1 : D&D across apps using GetCursorPos () ... 

    First we have to import Win32's code using DllImport ....  [trivial stuff, refer to sample code in Win32.cs ]

    Next we create an instance of a Window, which will contain a visual brush of the element we are dragging ... 

    private Window _dragdropWindow = null;
            private void CreateDragDropWindow(Visual dragElement)
            {
                System.Diagnostics.Debug.Assert(this._dragdropWindow == null);
                System.Diagnostics.Debug.Assert(dragElement != null);
                // TODO: FE? or UIE??   FE cause I am lazy on size . 
                System.Diagnostics.Debug.Assert(dragElement is FrameworkElement); 
    
                this._dragdropWindow = new Window();
                _dragdropWindow.WindowStyle = WindowStyle.None;
                _dragdropWindow.AllowsTransparency = true;
                _dragdropWindow.AllowDrop = false;
                _dragdropWindow.Background = null;
                _dragdropWindow.IsHitTestVisible = false;
                _dragdropWindow.SizeToContent = SizeToContent.WidthAndHeight;
                _dragdropWindow.Topmost = true;
                _dragdropWindow.ShowInTaskbar = false;
    
                _dragdropWindow.SourceInitialized += new EventHandler(
                delegate(object sender, EventArgs args)
                {
    
                    //TODO assert that we can do this.. 
                    PresentationSource windowSource = PresentationSource.FromVisual(this._dragdropWindow);
                    IntPtr handle = ((System.Windows.Interop.HwndSource)windowSource).Handle;
    
                    Int32 styles = Win32.GetWindowLong(handle, Win32.GWL_EXSTYLE);
                    Win32.SetWindowLong(handle, Win32.GWL_EXSTYLE, 
    styles | Win32.WS_EX_LAYERED | Win32.WS_EX_TRANSPARENT); }); Rectangle r = new Rectangle(); r.Width = ((FrameworkElement)dragElement).ActualWidth; r.Height = ((FrameworkElement)dragElement).ActualHeight; r.Fill = new VisualBrush(dragElement); this._dragdropWindow.Content = r; // put the window in the right place to start UpdateWindowLocation(); }

    Notice:

    1) I set the style to Transparent, layered window (this is ok since the window is small and it is only used for drag & drop )..  and 

    2) the call to UpdateWindowLocation () this is the code that positions the Window wherever the cursor is now..

    3) I likely need more error checking  

    The code in UpdateWindowLocation is straight forward:

     void UpdateWindowLocation()
            {
                if (this._dragdropWindow != null)
                {
                    Win32.POINT p;
                    if (!Win32.GetCursorPos(out p))
                    {
                        return;
                    }
                    this._dragdropWindow.Left = (double)p.X;
                    this._dragdropWindow.Top = (double)p.Y;
                }
            }

    This UpdateLocation code of course needs to be called whenever the cursor moves...  so we need some kind of callback during the drag operation.. We will use QueryContinueDrag for that..

    So, I go back to  the code in StartDrag ()  and wire up the event, as well as some code to show the window and destroy it after drag & drop:

     private void StartDragWindow(MouseEventArgs e)
            {
    
                GiveFeedbackEventHandler feedbackhandler = new GiveFeedbackEventHandler(DragSource_GiveFeedback); ;
                this.DragSource.GiveFeedback += feedbackhandler; 
                QueryContinueDragEventHandler queryhandler = new QueryContinueDragEventHandler(DragSource_QueryContinueDrag);
                this.DragSource.QueryContinueDrag += queryhandler; 
                IsDragging = true;
                CreateDragDropWindow(this.dragElement); 
                DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
                this._dragdropWindow.Show(); 
                DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
                DestroyDragDropWindow(); 
                IsDragging = false;
                this.DragSource.GiveFeedback -= feedbackhandler;
                this.DragSource.QueryContinueDrag -= queryhandler; 
            }

    The one thing to notice is that I still have GiveFeedbackHandler wired.. Why ?? We are no longer using the cursor...  but we still have to tell Drag & Drop not to use the default cursors..

     

    Using Visual for Feedback.2: Using DragOver to avoid the interop code and/or to limit dragging scope to my app.. 

    There is a slightly different approach you can use if you are drag & dropping just inside your app or have a smaller scope ... I some times use this approach because it allows me to avoid interop, avoid creating extra windows, and better control the scope of the drag...  


    Here is the full explanation of how it works and why it feels like hackalicious.

    When you call DoDragDrop, there is no Mouse or Cursor Events being fired in your WPF app...  OLE does the work for you and it moves cursor directly :(...  however, all of the Drag events are being fired... 

    We already know of the two events we can tap into from the source: GiveFeedback and QueryContinueDrag...    however neither of these events gives us access to the mouse or cursor position during the drag operation :( ...   We can however tap into the  Dragover  event; DragOverEventArgs has a GetPosition ( ) method that does the trick...    DragOver however is fired in the target, not the source.

    So, how would we do it??  Well , DragEvents are routed events.. they bubble up.. if we define a "Drag Scope"  within our app that we know is guaranteed to bubble the DragOver, then we can listen for it ...   the obvious choice for that scope is our Application's Window; this gives us access to any thing in our app; the scope could be smaller of course... 


    Here is how we wire that: 

    private void StartDragInProcAdorner(MouseEventArgs e)
            {
    
                // Let's define our DragScope .. In this case it is every thing inside our main window .. 
                DragScope = Application.Current.MainWindow.Content as FrameworkElement;
                System.Diagnostics.Debug.Assert(DragScope != null);
    
                // We enable Drag & Drop in our scope ...  We are not implementing Drop, so it is OK, but this allows us to get DragOver 
                bool previousDrop = DragScope.AllowDrop;
                DragScope.AllowDrop = true;            
    
                // Let's wire our usual events.. 
                // GiveFeedback just tells it to use no standard cursors..  
    
                GiveFeedbackEventHandler feedbackhandler = new GiveFeedbackEventHandler(DragSource_GiveFeedback);
                this.DragSource.GiveFeedback += feedbackhandler;
    
                // The DragOver event ... 
                DragEventHandler draghandler = new DragEventHandler(Window1_DragOver);
                DragScope.PreviewDragOver += draghandler; 
    
                // Drag Leave is optional, but write up explains why I like it .. 
                DragEventHandler dragleavehandler = new DragEventHandler(DragScope_DragLeave);
                DragScope.DragLeave += dragleavehandler; 
    
                // QueryContinue Drag goes with drag leave... 
                QueryContinueDragEventHandler queryhandler = new QueryContinueDragEventHandler(DragScope_QueryContinueDrag);
                DragScope.QueryContinueDrag += queryhandler; 
    
                //Here we create our adorner.. 
                _adorner = new DragAdorner(DragScope, (UIElement)this.dragElement, true, 0.5);
                _layer = AdornerLayer.GetAdornerLayer(DragScope as Visual);
                _layer.Add(_adorner);
    
    
                IsDragging = true;
                _dragHasLeftScope = false; 
                //Finally lets drag drop 
                DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
                DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
    
                 // Clean up our mess :) 
                DragScope.AllowDrop = previousDrop;
                AdornerLayer.GetAdornerLayer(DragScope).Remove(_adorner);
                _adorner = null;
    
                DragSource.GiveFeedback -= feedbackhandler;
                DragScope.DragLeave -= dragleavehandler;
                DragScope.QueryContinueDrag -= queryhandler;
                DragScope.PreviewDragOver -= draghandler;  
    
                IsDragging = false;
            }
      

    Explanations:

    • GiveFeedback is the same than before we use it to set no default cursor ..
    • Dragover on our DragScope  is what will let us move the cursor around..  These events are wired in the Drop target, not in the source control..
    • DragLeave is optional; the reason I wired it is because when the mouse leaves the scope, I want to cancel the Drag operation altogether, nix it!  So I subscribe to DragLeave to know when mouse left.. Unfortunately, I can't cancel the drag in DragLeave, so I set a flag to be read in QueryContinueHandler. QCH reads this flag and when set to true,  it sets the Action to Cancel in the drag to nix it..
    • The rest is creating our adorner, and the drag drop ..  plus all the clean up ... 

    There is a common gotcha with the DragLeave part of this scenario. The scope tends to always be a panel,grid, etc.. ( a container) and if the container has background to null, it is not hittesting, so you won't get the dragleave...   You have to explicitly set the Background="Transparent" to make sure you get it...   (you can see it in my sample with the Grid)..

    That is it for Drag ...   I hope I explained how to do the Drag part  of a drag  & drop.     I want to cut part 1 here so that you have a pretty clean sample of the "drag" .. 

    The source for every thing above is here.   

     

    You will have to tweak the MouseMove function to select which drag approach to use.. Just make sure you have at most one of these functions uncommented at any time..

    // StartDrag(e);
    //  StartDragCustomCursor(e);
    // StartDragWindow(e);
    StartDragInProcAdorner(e);

     

    Since I did not wire a Drop handler, for testing this, just "Drop" into some thing that handles Text like Microsoft Word..

    In part 2, I will cover the drop .. and in part 3 I will share the complete code with a couple of extra things that I omitted here to try to keep it clean  (some of them might be in the code sample)

  • Jaime Rodriguez

    forcing a WPF binding to 'refresh' ...

    • 2 Comments

    From an internal DL ... I thought this would be handy --- must admit is nicer than some hacks I have done in the past to accomplish same thing..

    ---  

    Sent: Tuesday, July 03, 2007 2:00 PM
    Subject: RE: forcing a rebuild of an items control...

     

    You’ll need to know the dependency object and dependency property where the binding was defined, which may not qualify as elegant.  Then you can use this line:

     

    BindingOperations.GetBindingExpressionBase(dependencyObject, dependencyProperty).UpdateTarget();

     

    --Eric

     

    Sent: Tuesday, July 03, 2007 1:08 PM
    Subject: forcing a rebuild of an items control...

     

    I have a control that has a list of stuff, and once I make a change to some unrelated thing I really need to force the binding to re-run… is there any elegant way to make this happen? Something like “InvalidateMeasure” but for binding… ?

     

  • Jaime Rodriguez

    Getting Expression Blend May Preview to launch Visual Studio when you click on a JS file..

    • 0 Comments

    When working on Silverlight 1.0 projects.. it kind of annoyed me that  Blend launches notepad to edit my JS file...    I tried going into Windows and tell it to always "open" JS files with Visual Studio...   (via OpenWith in the shell ) ... but Blend kept launching notepad..  why??

    The shell associates the "open" command with VS .. that was working fine.. but blend used the "Edit" command instead of "open" ...  So, I had to configure Windows to "edit"  .JS files with Visual Studio by editing the registry  entry at
    HKEY_CLASSES_ROOT\JSFile\Shell\Edit\Command\(Default), the command is set to Notepad and I had to edit the registry entry and change it to point to devenv.exe:
    C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe     [for a default location VS 2005  install]
    or C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe   [for a default location VS 2008 "Orcas" installed" ] ...

     Now, when I double click on a JS file in Blend May Preview, it launches VS ...

     

Page 1 of 1 (7 items)