Archive

Posts Tagged ‘UI’

HowTo: Hide HTML markup from non-signedin users at MonoX Social CMS

At MonoX Social CMS, which I use at both ClipFlair Social and Trafilm websites, I was in the need of hiding some HTML markup when the user is not signed-in.

The solution for this is to add runat="server" to the HMTL element one wants to hide and then set the Visible property that the object acquires due to the runat clause. The Visible property is set using some special syntax to access the MonoX API like below:

<ul runat="server"
      Visible="<% $Code: Page.User.Identity.IsAuthenticated %>"  >

</ul>

Categories: Posts Tags: , , , , , , ,

HowTo: Drop files onto Silverlight controls

I was recently adding drop-files support to ClipFlair Studio, so I had to do some research on the related API that is available to Silverlight apps.

Silverlight supports a limited set of Drag-and-Drop interaction with the operating system (supposedly for security reasons, but most probably because of the classic cross-platform implementation pains).

For example it allows you to drop a collections of file objects from the operating system’s file explorer (or other similar application) onto a Silverlight drop target, but not the other way around.

Also, it doesn’t allow dropping other flavors of content, like text, images etc., only collections of file objects.

To allow dropping files onto a Silverlight control you set AllowDrop property to true and handle the Drop and optionally the DragEnter/DragOver/DragLeave events to provide visual feedback during the drop operation (mostly when over the drop target, unless you do Mouse Capturing).

<RichTextBox
  x:Name="rtb"
  …
  AllowDrop="True" 
  Drop="rtb_Drop"
  DragEnter="rtb_DragEnter"
  DragOver="rtb_DragOver"
  DragLeave="rtb_DragLeave"

  />

 

  #region DragAndDrop

    private void rtb_Drop(object sender, System.Windows.DragEventArgs e)
    {
      VisualStateManager.GoToState(this, "Normal", true);

      //the Drop event passes in an array of FileInfo objects for the list of files that were selected and drag-dropped onto the RichTextBox.
      if (e.Data == null)
        return;

     IDataObject f = e.Data as IDataObject;
      if (f != null) //checks if the dropped objects are files

      {
        object data = f.GetData(DataFormats.FileDrop); //Silverlight only supports FileDrop
        FileInfo[] files = data as FileInfo[]; //…GetData returns null if format is not supported

        e.Handled = true;

        if (files != null)

          //Walk through the list of FileInfo objects of the selected and drag-dropped files and parse the .txt and .docx files
          //and insert their content in the RichTextBox.
          foreach (FileInfo file in files)
            Load(file, false);
      }

    }

    private void rtb_DragEnter(object sender, System.Windows.DragEventArgs e)
    {
      VisualStateManager.GoToState(this, "DragOver", true);
      e.Handled = true;
    }
   
    private void rtb_DragOver(object sender, System.Windows.DragEventArgs e)
    {
      e.Handled = true;
      //NOP
    }

    private void rtb_DragLeave(object sender, System.Windows.DragEventArgs e)
    {
      VisualStateManager.GoToState(this, "Normal", true);
      e.Handled = true;
    }

    #endregion

 

For the visual feedback you can use VisualStateManager and respective VisualStates at say a Grid that wraps the drop target in the XAML layout.

<Grid x:Name="LayoutRoot">
   <VisualStateManager.VisualStateGroups>
     <VisualStateGroup x:Name="DragStates">
       <VisualStateGroup.Transitions>
         <VisualTransition GeneratedDuration="0:0:0.3">
           <VisualTransition.GeneratedEasingFunction>
             <CircleEase EasingMode="EaseIn"/>
           </VisualTransition.GeneratedEasingFunction>
         </VisualTransition>
       </VisualStateGroup.Transitions>
       <VisualState x:Name="Normal"/>
       <VisualState x:Name="DragOver">
         <Storyboard>
           <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Control.Background).(SolidColorBrush.Color)" Storyboard.TargetName="rtb">
             <EasingColorKeyFrame KeyTime="0" Value="#FFFFF7D1"/>
           </ColorAnimationUsingKeyFrames>
         </Storyboard>
       </VisualState>
     </VisualStateGroup>
   </VisualStateManager.VisualStateGroups>

 

Note that on MacOS-X Safari doesn’t pass drop events automatically to plugins and you have to catch them via Javascript and pass them to Silverlight, plus I’ve read that in MacOS-X Firefox doesn’t support this at all.

Even worse, Microsoft’s workaround article has a typo in the Javascript (should call dragDrop instead of drop) and there is a chance it doesn’t work in latest version of Safari. For more see:

http://msdn.microsoft.com/en-us/library/ee670998%28v=vs.95%29.aspx

http://social.msdn.microsoft.com/Forums/silverlight/en-US/42a6b672-7d26-4690-be80-2149da755020/silverlight-4-detect-file-drop-event-on-mac?forum=silverlightarchieve&prof=required

http://community.rightpoint.com/blogs/viewpoint/archive/2011/03/27/silverlight-4-file-drag-and-drop-on-firefox-on-mac.aspx

http://www.telerik.com/forums/dropping-files-using-safari-on-mac (this says one can use HTML5 events and the Silverlight HTML/Javascript Bridge to notify Silverlight)

http://www.thebuzzmedia.com/html5-drag-and-drop-and-file-api-tutorial/

Gotcha: OnLostMouseCapture always called by CaptureMouse at WPF

It seems that CaptureMouse is behaving differently in WPF and Silverlight, in that in the former one it immediately calls OnLostMouseCapture at a Visual, whereas in Silverlight it doesn’t get called if the element didn’t have the mouse capture already (btw, in Silverlight that method is at a UIElement – there is no Visual ancestor as in WPF).

    #region --- Events ---

    private void OnMouseLeftButtonDown(object source, MouseButtonEventArgs e)
    {
      if (IsMoveToPointEnabled)
      {
        MoveThumbToPoint(e.GetPosition(this));
        CaptureMouse(); //must do before setting dragging=true, since WPF seems
//to be always calling OnLostMouseCapture on all
//controls, even if they didn't have the mouse capture
dragging = true; //always set, we might not make it to capture the mouse } } private void OnMouseLeftButtonUp(object source, MouseButtonEventArgs e) { ReleaseMouseCapture(); dragging = false; //always clear, in case we never had the mouse capture } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); if (dragging && IsMoveToPointEnabled) MoveThumbToPoint(e.GetPosition(this)); } protected override void OnLostMouseCapture(MouseEventArgs e) { base.OnLostMouseCapture(e); dragging = false; //set dragging to false whatever the value of
//IsMoveToPointEnabled (may have changed while dragging)
} #endregion

 

The rest of the code of the SliderExt class that I’ve just added to ClipFlair codebase is below, where … is where the code region that was quoted above goes. This class is a descendent of Slider and implements IsMoveToPointEnabled property for Silverlight (in WPF that property exists), plus in WPF it fixes the behavior of the control so that when that property is set, it not only supports moving the slider thumb to the point on its track where mouse button was held down (instead of stepping up/down as is usually done in scrollbars), but also supports dragging from any point in the slider track which WPF’s implementation of IsMouseToPointEnabled didn’t do (it only allowed to drag from the thumb).

//Project: ClipFlair (http://ClipFlair.codeplex.com)
//Filename: SliderExt.cs
//Version: 20140313

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace SliderExtLib
{
  public class SliderExt : Slider
  {

    public SliderExt()
    {

      //peek into mouse button events (even handled ones)
AddHandler(MouseLeftButtonDownEvent,
new MouseButtonEventHandler(OnMouseLeftButtonDown), true); AddHandler(MouseLeftButtonUpEvent,
new MouseButtonEventHandler(OnMouseLeftButtonUp), true); } #region --- Fields --- protected bool dragging; #endregion #region --- Properties --- public bool IsMoveToPointEnabled //WPF has this prop (hiding it), doesn't
//implement move to point if you click and drag on the track instead of thumb
{ get { return (bool)GetValue(IsMoveToPointEnabledProperty); } set { SetValue(IsMoveToPointEnabledProperty, value); } } public static readonly DependencyProperty IsMoveToPointEnabledProperty = DependencyProperty.RegisterAttached("IsMoveToPointEnabled", typeof(bool), typeof(SliderExt),
new PropertyMetadata(false)); #endregion #region --- Methods --- private void MoveThumbToPoint(Point p) { if (Orientation == Orientation.Horizontal) Value = p.X / ActualWidth * Maximum; else //if (Orientation == Orientation.Vertical) Value = p.Y / ActualHeight * Maximum; } #endregion




...



}
}

The incentive of making such a slider was for using it at the ColorPicker control I’ve just added to the ClipFlair codebase.

HowTo: find max ZIndex from a collection of UIElements with LINQ

Based on a relevant answer at

http://stackoverflow.com/questions/1101841/linq-how-to-perform-max-on-a-property-of-all-objects-in-a-collection-and-ret

and some digging into LINQ’s Aggregate method documentation is what I’ve just added to my enhanced version of SilverFlow library’s FloatingWindowHost (copying from FloatingWindowHost.cs at http://clipflair.codeplex.com source code)

        /// <summary>
        /// Sets the specified UIElement topmost.
        /// </summary>
        /// <param name="element">UIElement to set topmost.</param>
        /// <exception cref="ArgumentNullException">UIElement is null</exception>
        private void SetTopmost(UIElement element)
        {
            if (element == null)
                throw new ArgumentNullException("element");

            Canvas.SetZIndex(element, MaxZIndex + 1);
        }

        public int MaxZIndex
        {
          get {
            return FloatingWindows.Aggregate(-1, (maxZIndex, window) => {
              int w = Canvas.GetZIndex(window);
              return (w > maxZIndex) ? w : maxZIndex; 
            });  //Math.Max would be cleaner, but slower to call
          }
        }

Worth noting regarding the code above is that Canvas.ZIndex is an attached property available for UIElements in various containers, not just used when being hosted in a Canvas (see http://stackoverflow.com/questions/569751/controlling-rendering-order-zorder-in-silverlight-without-using-the-canvas-con)

Guess one could even make a SetTopmost and SetBottomMost static extension method for UIElement easily by adapting this code.

Gotcha: .NET Point and PointConverter inconsistency in string format used

I have submitted the following issue to Microsoft Connect (product feedback center):

http://connect.microsoft.com/VisualStudio/feedback/details/809084/point-class-issue-with-two-way-databinding

Point class issue with two-way databinding

 

In Silverlight, when using databinding code like the following:
<StackPanel Orientation="Vertical" Name="propPosition">
    <sdk:Label Style="{StaticResource PropertyLabelStyle}" Content="Position:" Target="{Binding ElementName=edPosition}" />
     <TextBox Name="edPosition" Text="{Binding Position, Mode=TwoWay, ValidatesOnExceptions=True, NotifyOnValidationError=true}" />
</StackPanel>

and binding (via DataContext) to a View that has the following property:

Point Position { get; set; }

then what you see on the UI is values like 500; 100

but when you try to edit the position and enter say 400; 100 it shows a red message that it is invalid format (caught an exception that is and showing on the UI automatically cause of ValidatesOnExceptions and NotifyOnValidationError being true)
if you enter 400, 100 it works ok (it moves a ClipFlair Studio [http://ClipFlair.net] window arround in my case), so it outputs the two numbers (X, Y) with a ";" but expects to get them separated with a "," (also wonder if it copes OK with numbers in say format used in Greece where decimal point separator is , instead of .)

 

I managed to reproduce the same behaviour in a .NET console program:

//Console program in C# to demonstrate Point.ToString and PointConverter (from string) inconsistency in string format
//(the former outputs X;Y, the later expects X,Y)

using System;
using System.Windows; //for "Point" class (needs WindowsBase.dll reference)

namespace ConsoleApplication1
{
  class Program
  {

    static PointConverter conv = new PointConverter();

    static void Main(string[] args)
    {
      Point p = new Point(10, 20);
      Console.WriteLine(p); //outputs "10;20"
           
      //——————

      Point p1 = (Point)conv.ConvertFrom("10, 20");
      Console.WriteLine(p1); //outputs "10;20"

      //——————

      Point p2 = (Point)conv.ConvertFrom("10; 20"); //unhandled exception "System.FormatException" in mscorlib.dll
      Console.WriteLine(p2); //never reached cause of exception above
    }

  }
}

HowTo: use a Timer component for delayed execution in WinForms

This is my contribution to:

http://stackoverflow.com/questions/303116/system-windows-threading-dispatcher-and-winforms

Sometimes a Timer component is useful and easy to setup in WinForms, just set its interval and then enable it, then make sure the first thing you do in its Tick event handler is to disable itself.

I think Timer runs the code in its own thread, so you may still need to do a BeginInvoke (called upon the WinForm object [this]) to run your Action.

 

private WebBrowserDocumentCompletedEventHandler handler;
//need to make it a class field for the handler below
//(anonymous delegates seem to capture state at point of
// definition, so they can't capture their own reference)
private string imageFilename; private bool exit; public void CaptureScreenshot(
Uri address = null,
string imageFilename = null,
int msecDelay = 0,
bool exit = false) { handler = (s, e) => { webBrowser.DocumentCompleted -= handler; //must do first this.imageFilename = imageFilename; this.exit = exit; timerScreenshot.Interval = (msecDelay>0)? msecDelay : 1; timerScreenshot.Enabled = true; }; webBrowser.DocumentCompleted += handler; Go(address); //if address == null, will use URL from UI } private void timerScreenshot_Tick(object sender, EventArgs e) { timerScreenshot.Enabled = false; //must do first BeginInvoke((Action)(() => //Invoke at UI thread { //run in UI thread BringToFront(); Bitmap bitmap = webBrowser.GetScreenshot(); if (imageFilename == null) imageFilename = bitmap.ShowSaveFileDialog(); if (imageFilename != null) { Directory.CreateDirectory(
Path.GetDirectoryName(
Path.GetFullPath(imageFilename)));
//create any parent directories needed bitmap.Save(imageFilename); } bitmap.Dispose(); //release bitmap resources if (exit) Close(); //this should close the app, this is main form }), null); }

You can see the above in action at ClipFlair‘s WebCapture tool (http://gallery.clipflair.net/WebCapture, source code at: http://ClipFlair.codeplex.com, see Tools/WebCapture folder) that grabs screenshots from websites.

BTW, if you want to call the executable from command-line make sure you go to Properties of the project and at Security tab turn-off ClickOnce security (else it can’t access command-line).

Fix: Silverlight Media Framework Player VolumeElement out of sync

I was just adding a storable (persistent) Volume property to the MediaPlayerView class used at ClipFlair’s MediaPlayerWindow (connected to the underlying SMF player’s VolumeLevel property), when I realized that after reloading saved state, the SMF player’s Volume control would show a different value than the value set to it (which I could confirm by flipping the MediaPlayer control and looking at its properties on the backpanel that the ClipFlair app features for each component and for the activity container itself).

It seems to be some bug in the SMF logic or in its default template, since the following fix makes the issue disappear. The trick is that at a descendent class from SMFPlayer (like the MediaPlayer class that ClipFlair’s MediaPlayerWindow uses), one can override the OnApplyTemplate method (a standard method in templated XAML-based controls at Silverlight and WPF) and call the UpdateVolumeElement method shown below.

That method temporarily sets the volume to another value than its current one (this is important, just setting again to the same value would be ignored), then set again the current value to force the VolumeElement control’s UI to update.

 

//Project: ClipFlair (http://ClipFlair.codeplex.com)
//Filename: MediaPlayer.cs
//Version: 20130211

using System;
using System.Linq;
using System.Windows;
using System.Windows.Media;

using Microsoft.SilverlightMediaFramework.Core;
using Microsoft.SilverlightMediaFramework.Core.Media;
using Microsoft.SilverlightMediaFramework.Core.Accessibility.Captions;
using Microsoft.SilverlightMediaFramework.Plugins.Primitives;

namespace ClipFlair.MediaPlayer
{

  public class MediaPlayer : SMFPlayer
  {

    public override void OnApplyTemplate()
    {
      base.OnApplyTemplate();

      //...
      UpdateVolumeElement(); //patch for SMF bug
    }

    protected void UpdateVolumeElement()
    {
      //patch for SMF to update VolumeElement UI with any already set VolumeLevel
      double volume = VolumeLevel;
      VolumeLevel = (volume == 1) ? 0.9 : 1;
      VolumeLevel = volume;
    }

//...

 

Update: While submitting this as a bug to SMF source site on Codeplex it came to me that I could try setting the VolumeElement’s VolumeLevel value directly at ApplyTemplate and indeed it works:

protected void UpdateVolumeElement() 
{ //patch for SMF to update VolumeElement UI with any already set VolumeLevel VolumeElement.VolumeLevel = VolumeLevel; }
%d bloggers like this: