Hello all,
A post in french :
Je relaie les informations postées par moi-même sur un autre blog : Blog expertease Alti
C'est en français !
Hello all,
A post in french :
Je relaie les informations postées par moi-même sur un autre blog : Blog expertease Alti
C'est en français !
The most popular controls which has been brought by the Microsoft SDK is certainly the scatterView. Each item is positioned at a random place with a random orientation.
You can then rotate, move or scale them with your fingers. Here we will focus on this last point : the scaling. This is a really nice feature and you may wants to put it in your application (it may also be replace my a mouse wheel or stylus events, etc.).
If an user wants to zoom-in on a specific part of the presented items, he wills do a 'scale manipulation' with it's fingers on the specific part.
Simple, will you think : we just have to change the width and the height of the control based on the scale delta ! But the problem is that, the control will grow but the specific part wanted by the user will no more be under it's fingers. A figure worth a thousand words :
Here we are going to scale a scatterViewItem with the property 'CanMove' set to false. We do it because, the scatterView item does already what we wants and this is done by a translation.
Also we are going to use a Affine2DManipulationProcessor which will gives us the scale value for a manipulation done by multiple fingers.
If some are catching stylus events, you could use a ManipulationProcessor from the multiTouch SDK (available here :http://www.microsoft.com/downloads/details.aspx?FamilyID=12100526-ed26-476b-8e20-69662b8546c1&displaylang=en).
The XAML :
<s:ScatterView VerticalAlignment="Stretch" HorizontalAlignment="Stretch"> <s:ScatterViewItem x:Name="_myObjectToScale" Orientation="0" CanRotate="False" CanScale="False" CanMove="False" Center="512.0,384.0" ShowsActivationEffects="False" PreviewContactDown="_myObjectToScale_ContactDown" PreviewContactUp="_myObjectToScale_ContactUp"> <Image Source="Resources/myself.jpg" /> </s:ScatterViewItem>
The code :
private Affine2DManipulationProcessor _ourManipProc; public Affine2DManipulationProcessor OurManipProc { get { return _ourManipProc; } set { _ourManipProc = value; } } public SurfaceWindow1() { InitializeComponent(); DataContext = this; _ourManipProc = new Affine2DManipulationProcessor(Affine2DManipulations.Scale, this); //Catch the event from our manipulation processor OurManipProc.Affine2DManipulationDelta += OurManipProc_Affine2DManipulationDelta; } private void _myObjectToScale_ContactDown(object sender, ContactEventArgs e) { //this contact is tracked by ou MP OurManipProc.BeginTrack(e.Contact); } private void _myObjectToScale_ContactUp(object sender, ContactEventArgs e) { //this contact is no more tracked by ou MP OurManipProc.EndTrack(e.Contact); }
Then the important part, the Affine2DManipulationDelta handler which will do what we wants, I will describe it below.
void OurManipProc_Affine2DManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e) { double scaleDelta = e.ScaleDelta; if (scaleDelta == 1.0) return; Point manipOrigin = e.ManipulationOrigin; Point oldCenter = new Point(_myObjectToScale.Center.X, _myObjectToScale.Center.Y); double oldHeight = _myObjectToScale.ActualHeight; double newHeight = _myObjectToScale.ActualHeight * scaleDelta; double oldWidth = _myObjectToScale.ActualWidth; double newWidth = _myObjectToScale.ActualWidth * scaleDelta; _myObjectToScale.Height = newHeight; _myObjectToScale.Width = newWidth; double ratioX = Math.Abs(manipOrigin.X - oldCenter.X) / (oldWidth / 2); double newCenterXD = ratioX * Math.Sign(oldCenter.X - manipOrigin.X) * (newWidth - oldWidth) / 2; double ratioY = Math.Abs(manipOrigin.Y - oldCenter.Y) / (oldHeight / 2); double newCenterYD = ratioY * Math.Sign(oldCenter.Y - manipOrigin.Y) * (newHeight - oldHeight) / 2; if (scaleDelta > 1.0) _myObjectToScale.Center += new Vector(newCenterXD, newCenterYD); else _myObjectToScale.Center += new Vector(newCenterXD, newCenterYD); }
First we need to calculate the new size of our control. This is done by multiplying it's actual size by the scaleDelta gived by our processor.
Then we store some interesting values as the old size, the old center position, etc.
Then we calculate the ration for X and for Y. What is it ? It's ratio of the aimed point (the point on top of which the manipulation is done) and the half of the control size. But why do we need it ? Because we wants the controls to grow on each side of the aimed point, not only the one near the center. If we does not calculate this, one side of the control would stay at the same position during our manipulation.
Next we calculate the center delta which is the translation we must operate on our control for the focused point to stay under our fingers (or mouse pointer, or stylus, whatever you wants :D).
We finaly apply all this measure to our control. That's it !
Today I have earned my Microsoft Certified Technology Specialist: .NET Framework 3.5, Windows Presentation Foundation Applications certification !
Youpi I can't put this nice logo on my blog !
Hello,
Often when you read articles explaining how to use a D3DImage in your WPF application you use code which directly handle the CompositorTarget.Rendering event by updating the 3D world... This can lead to performance problems.
For example in my application, WPF refresh the display with a FPS of 160 : the handler which recreate the 3D image is then call 160 times a second. No need to refresh this often.
The solution I used is to create a Timer which will do it at the FPS I want. Let's say 30FPS for example :
public void createTheRenderingWithCorrectFPS(int whichFPS){ DispatcherTimer _timer = new DispatcherTimer(); _timer.Interval = new TimeSpan(1000 /whichFPS); _timer.Start(); _timer.Tick += new EventHandler(_timer_Tick); } void _timer_Tick(object sender, EventArgs e) { //Refresh the 3D scene }
This enables your application to be really more reactive especially that you need to be on the main thread to update the 3D scene...
When you defines a material in Ogre or in 3D engines in general, you can play with at least four differents parameters : Ambient color, diffuse color, emissive color and specular color.
In this post I will give you the definitions and show you some example for those who - like me - requires some visual example to understand better...
Here are the definitions (found on the web...) :
Ambient color : Ambient color is the color of an object where it is in shadow. This color is what the object reflects when illuminated by ambient light rather than direct light.
Diffuse color :Diffuse color is the most instinctive meaning of the color of an object. It is that essential color that the object reveals under pure white light. It is perceived as the color of the object itself rather than a reflection of the light.
Emissive color : This is the self-illumination color an object has.
Specular color :Specular color is the color of the light of a specular reflection (specular reflection is the type of reflection that is characteristic of light reflected from a shiny surface).
All object material can so define these 4 parameters. The color will then depend on them but also of the light they receive.
All the example are done with a point light, placed at the position (350, 400, 800);
With this type of light you define two colors, the ambient one and the diffuse one.
The light parameters are :
The light parameters are :
The light parameters are :
As a conclusion : nothing is better than experimentation !
Hello,
Here is a little post to show you a little video presenting the project I have worked on during my 6 months of training. I am very proud of it :
The subject was to find the new way of representations and manipulations of OLAP Data without using a keyboard or a mouse.
This is done in 3D on the Microsoft Surface plateform.
The technologies involved are :
Here are some links for those interested :
People who worked on it :
Enjoy !
Sometimes you need to animate your specific object and for this purpose there is the WPF animation.
Here are the prerequireds :
The difficulty resides in being able to create an animation, create a storyboard and set the corrects values for the attached properties - TargetName and TargetProperty - on the animation. All of this directly in the code.
You can then control the animation, the usual way than in XAML.
Here is how I did it, step by step.
First I make my business object derives from FrameWorkElement. Why ?
Because, this make my object a dependency object. Also, my object will implements IAnimatable ( FrameWorkElement heritate from UIElement which implements IAnimatable). And finally, it gets a NameScope which will be used later.
So here is my object:
public class Puppet: FrameworkElement { }
Next I will had a DependencyProperty to be animated. Here I animate a value of type Point. I create all the usual things for the dependencyProperty and also add a storyboard (I will always use the same):
public class Puppet: FrameworkElement { public static DependencyProperty ContactMovementProperty = DependencyProperty.Register("ContactMovement", typeof(Point), typeof(Puppet)); public Point ContactMovement { get { return (Point)GetValue(SatelliteNavigator.ContactMovementProperty); } set { SetValue(SatelliteNavigator.ContactMovementProperty, value); } } private Storyboard stboard = new Storyboard(); }
Then, let's say I will create and launch the animation directly in the constructor. Here is the code added :
public class() { //Maybe it was running before (if not set in the constructor) //stboard.Stop(); double xFinal = 36; double yFinal = 36; PointAnimation animation = new PointAnimation(); animation.From = ContactMovement; animation.To = new Point(xFinal , yFinal ); animation.Duration = TimeSpan.FromMilliseconds(e.Velocity.Length*7 / deceleration); animation.AccelerationRatio = 0f; animation.DecelerationRatio = 0.4f; String puppetName= "puppet"; NameScope nams = new NameScope(); NameScope.SetNameScope(this, nams); this.RegisterName(puppetName, this); Storyboard.SetTargetName(animation, puppetName); Storyboard.SetTargetProperty(animation, new PropertyPath(Puppet.ContactMovementProperty)); stboard.Children.Add(animation); stboard.Begin(this); }
What is done ? First I create the animation with random values for our example.
Then I create a NameScope and set it to our object. Why ? Because it's not created by the runtime for our object and we need one. It will be used by the animation to retrieve the object to animate.
This is done by registering the Puppet object in the namescope and giving the same TargetName of the attachedProperty of the animation(Storyboard.SetTargetName).
Then we clear the children animation of the storyboard (maybe it was not empty) and launch the storyboard by giving it the Puppet (object in which the animated object is).
We can then use the usual methods on storyboard to control the play of the media. For example :
stboard.Stop();
As you can see this is not as easy as writing some XAML lines but it's not impossible !
Any questions ?
Hello,
Today a post about creating a PNG from a XAML file. Easy some will says but we will see some more tips :
This snippet will use a XAMLReader, and create a visual from the XAML and the put it inside you application :
Microsoft.Win32|>.OpenFileDialog dialog = new Microsoft.Win32|>.OpenFileDialog();
dialog.Title = "Select the XAML file.";
dialog.AddExtension = true;
dialog.CheckFileExists = true;
dialog.DefaultExt = ".xaml";
dialog.Filter = "Xaml files |*.xaml";
if (dialog.ShowDialog() == true)
{
String path = dialog.FileName;
UIElement visual = XamlReader.Load(System.Xml.XmlReader.Create(path)) as UIElement;
if (visual != null)
{
_docker.Children.Add(visual);
}
else
{
MessageBox.Show("Cannot load the UiElement from the XAML.", "Error", MessageBoxButton.OK);
this.Close();
}
}
Quite simple in fact.
Then to create a sample from a control or anything which is a visual you will use a different syntax than the one presented sooner in this blog.
The tips is to create a brush from the visual, and fill a Rectangle in a drawingContext.
Here is the code :
Visual theVisual = _docker; //Put the aimed visual here. //Get the size you wants from the UI double width = Convert.ToDouble(_widthTextB.Text); double height = Convert.ToDouble(_heightTextB.Text); if (double.IsNaN(width) || double.IsNaN(height)) { throw new FormatException("You need to indicate the Width and Height values of the UIElement."); } Size size = new Size(width, height); DrawingVisual drawingVisual = new DrawingVisual(); VisualBrush vBrush = new VisualBrush(theVisual); using (DrawingContext dc = drawingVisual.RenderOpen()) { dc.DrawRectangle(vBrush, null, new Rect(new Point(), size)); } RenderTargetBitmap render = new RenderTargetBitmap( Convert.ToInt32(1900), Convert.ToInt32(1200), 96, 96, PixelFormats.Pbgra32); // Indicate which control to render in the image render.Render(drawingVisual); Stream oStream = new FileStream("out.png", FileMode.Create); PngBitmapEncoder encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(render)); encoder.Save(oStream); oStream.Flush(); oStream.Close();
There is a little drawback: the xaml visual you load must be configured to stretch when putted inside a layout control...
Here is some screenShot of the app running :
The code source is attached to the post.
To display informations into your scene you can use a billboard represented by the MovableText into (M)ogre but sometimes you just want to put some static text somewhere because it's more readable.
For example :
Here is the code which use a lot of my last article .
The steps are :
The part which may be interest you is how to get the right size for the created bitmap based on the text...
Also the creation of the manualObject is not necessary but I think it may interest some people to see how to use it.
/// <summary>
/// Creates a 'poster' based on a text.
/// </summary>
/// <param name="Smgr">The scenemanager (necesary to create the manual object).</param>
/// <param name="text">The text to put on the poster.</param>
/// <author>Jonathan ANTOINE</author>
private static ManualObject createALabel(SceneManager Smgr, String text)
{
String textureName = Guid.NewGuid().ToString();
System.Drawing.Font font = new System.Drawing.Font("Calibri", 12, FontStyle.Bold);
Bitmap bitmap = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(bitmap);
SizeF measureString = g.MeasureString(text, font);
bitmap = new Bitmap((int)measureString.Width, (int)measureString.Height);
g = Graphics.FromImage(bitmap);
g.FillRectangle(Brushes.Black, new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height));
g.DrawString(text, font, new System.Drawing.SolidBrush(Color.White), new Point(3, 3));
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
Stream oStream = new MemoryStream();
g.Save();
bitmap.Save(oStream, ImageFormat.Png);
oStream.Flush();
//bitmap.Dispose();
//Back to the start of the stream
oStream.Position = 0;
//read all the stream
BinaryReader oBinaryReader = new BinaryReader(oStream);
byte[] pBuffer = oBinaryReader.ReadBytes((int)oBinaryReader.BaseStream.Length);
oStream.Close(); //No more needed
TextureManager.Singleton.Remove(textureName); //Remove eventually texture with the same name
unsafe
{
GCHandle handle = GCHandle.Alloc(pBuffer, GCHandleType.Pinned);
byte* pUnsafeByte = (byte*)handle.AddrOfPinnedObject();
void* pUnsafeBuffer = (void*)handle.AddrOfPinnedObject();
MemoryDataStream oMemoryStream = new MemoryDataStream(pUnsafeBuffer, (uint)pBuffer.Length);
DataStreamPtr oPtrDataStream = new DataStreamPtr(oMemoryStream);
Mogre.Image oMogreImage = new Mogre.Image().Load(oPtrDataStream, "png");
TextureManager.Singleton.LoadImageW(textureName, ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME, oMogreImage);
//handle.Free();
}
String matNam = Guid.NewGuid().ToString();
MaterialPtr _dynamicMaterial = MaterialManager.Singleton.Create(matNam, ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME);
Pass pass = _dynamicMaterial.GetTechnique(0).GetPass(0);
pass.ShadingMode = ShadeOptions.SO_PHONG;
TextureUnitState tus = pass.CreateTextureUnitState(textureName);
tus.SetTextureAddressingMode(TextureUnitState.TextureAddressingMode.TAM_CLAMP);
pass.AddTextureUnitState(tus);
_dynamicMaterial.Dispose(); //Dispose the pointer, not the material !
ManualObject manualObject = Smgr.CreateManualObject(Guid.NewGuid().ToString());
manualObject.EstimateIndexCount(6);
manualObject.EstimateVertexCount(6);
manualObject.Begin(matNam, RenderOperation.OperationTypes.OT_TRIANGLE_LIST);
//DESSUS
int yWidthVariable = bitmap.Width;
int xWidth = bitmap.Height;
manualObject.Position(new Vector3(0, 0, 0));
manualObject.TextureCoord(1, 0);
manualObject.Normal(Vector3.UNIT_Z);
manualObject.Position(new Vector3(0, -yWidthVariable, 0));
manualObject.TextureCoord(0, 0f);
manualObject.Normal(Vector3.UNIT_Z);
manualObject.Position(new Vector3(xWidth, 0, 0));
manualObject.TextureCoord(1f, 1);
manualObject.Normal(Vector3.UNIT_Z);
manualObject.Position(new Vector3(0, -yWidthVariable, 0));
manualObject.TextureCoord(0f, 0f);
manualObject.Normal(Vector3.UNIT_Z);
manualObject.Position(new Vector3(xWidth, -yWidthVariable, 0));
manualObject.TextureCoord(0f, 1.0f);
manualObject.Normal(Vector3.UNIT_Z);
manualObject.Position(new Vector3(xWidth, 0, 0));
manualObject.TextureCoord(1f, 1.0f);
manualObject.Normal(Vector3.UNIT_Z);
manualObject.End();
manualObject.CastShadows = false;
return manualObject;
}
Also you can get the size of the "poster" by using the boundingbox. An example of use :
manualObject.BoundingBox.Size.x * 0.5f * Vector3.UNIT_Y;
The code can be find as an attached file.
Today we are going to learn how we can uses the powerful data binding of WPF even on non-WPF objects.
Sometimes you need to use the databinding with an object that you have not created and you can't use inheritance.
For example I wanted to use a Mogre Camera and build some WPF animation with it and I couldn't because :
Then I couldn't use the WPF animation to move my camera... next is how I solve it.
Here is the solution i use:
To control my camera, I created this DependencyProperty inside my windows:
public static readonly DependencyProperty CameraPositionProperty = DependencyProperty.Register("CameraPosition", typeof(Point3D), typeof(SurfaceWindow1), new PropertyMetadata(new Point3D(0, 0, 0))); public Point3D CameraPosition { get { return (Point3D)GetValue(CameraPositionProperty); } set { SetValue(CameraPositionProperty, value); } }
Then I added this override :
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { base.OnPropertyChanged(e); if (e.Property.Name.Equals("CameraPosition")) { _ogre.Camera.SetPosition((float)((Point3D)e.NewValue).X, (float)((Point3D)e.NewValue).Y, (float)((Point3D)e.NewValue).Z); _ogre.Camera.LookAt(Vector3.ZERO); } }
And my usual question :
When you build some animations with WPF, Surface or JavaFX you sometimes need to know how evolve an angle. For example, you have the new angle (orientation) of an object and you have store before the last measure of this orientation : how to calculate the evolution ?
"This is simple" will you say, just do this :
double difference = secondAngle - firstAngle;
But this snippet leads to errors. This is because the orientation value given is relative to a certain initial value, and restart to 0° if you pass the 360°. Lets give you an example: if the object was oriented at 358° and the user turns it of 5° you will obtain a difference of (3-358=) -355° where you actually wants to find 5....
A solution I propose is to consider that the methods is called enough frequently that if the object rotate to the left or to the right it has not enough time to rotate more than 180° (half a tour).
Based on this, we consider that the direction of the rotation is given by the "shortest way". If it shorter to turn to the left to go to the new angle, then we select the angle sign which tells we have turned to the left. It may be easiest to understand by looking atentivly to the image above.
An image is maybe better than word :
We then have this method in C#:
private double calculateDifferenceBetweenAngles(double firstAngle, double secondAngle) { double difference = secondAngle - firstAngle; while (difference < -180) difference += 360; while (difference > 180) difference -= 360; return difference; }
When do use it ? For example when you build a carousel: the user can click on a specific item and the carousel rotate so it is in front of you. You then need to have the correct angle. I found no other way to do it.
Hello,
Here is my first post about WPF and Mogre, maybe it will helps some people...
The subject of today is "How to use a screenshot of a WPF elements and put it as an texture on your Mogre object"...
Why ? Because WPF enable you to create very rich interface and so great image to place on you differents elements...
By the way, I let you follow the link at the end of the post to learn how to blend Mogre in WPF and how to take a screenShot of a WPF visual.
The steps to follow are these :
The original code is from thomas lebrun and can be found in any good WPF book :
Visual theVisual = this ; //Put the aimed visual here. double width = Convert.ToDouble(theVisual.GetValue(FrameworkElement.WidthProperty)); double height = Convert.ToDouble(theVisual.GetValue(FrameworkElement.HeightProperty)); if (double.IsNaN(width) || double.IsNaN(height)) { throw new FormatException("You need to indicate the Width and Height values of the UIElement."); } RenderTargetBitmap render = new RenderTargetBitmap( Convert.ToInt32(width), Convert.ToInt32(this.GetValue(FrameworkElement.HeightProperty)), 96, 96, PixelFormats.Pbgra32); // Indicate which control to render in the image render.Render(this); Stream oStream = new MemoryStream(); PngBitmapEncoder encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(render)); encoder.Save(oStream); oStream.Flush();
//Back to the start of the stream oStream.Position = 0; //read all the stream BinaryReader oBinaryReader = new BinaryReader(oStream); byte[] pBuffer = oBinaryReader.ReadBytes((int)oBinaryReader.BaseStream.Length); oStream.Close(); //No more needed TextureManager.Singleton.Remove(sName); //Remove eventually texture with the same name
unsafe { GCHandle handle = GCHandle.Alloc(pBuffer, GCHandleType.Pinned); byte* pUnsafeByte = (byte*)handle.AddrOfPinnedObject(); void* pUnsafeBuffer = (void*)handle.AddrOfPinnedObject(); MemoryDataStream oMemoryStream = new MemoryDataStream(pUnsafeBuffer, (uint)pBuffer.Length); DataStreamPtr oPtrDataStream = new DataStreamPtr(oMemoryStream); oMogreImage = oMogreImage.Load(oPtrDataStream, "png"); TextureManager.Singleton.LoadImageW(sName, ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME, oMogreImage); //handle.Free(); }
Here is the code of how you can create a material with this texture:
_dynamicMaterial = MaterialManager.Singleton.Create(SCREENSHOT_MATERIAL_NAME, ResourceGroupManager.DEFAULT_RESOURCE_GROUP_NAME); Pass pass = _dynamicMaterial.GetTechnique(0).GetPass(0); TextureUnitState tus = pass.CreateTextureUnitState(SCREENSHOT_TEXTURE_NAME); _dynamicMaterial.GetTechnique(0).GetPass(0).AddTextureUnitState(tus);
Then you just have to use it as a normal texture...
Here is a little screenshot of the results. I display a cube with the face using as a texture a screenshot of the window in which it is....
Hello,
Long time i didn’t post on this blog for some reasons :
No it’s ok and i am back to post some entries on this blog again…
I am doing my training on WPF and Surface so I widen the scope of the blog…
+++
Let's continue pur story about creating our own media player.
You can find a demo of the resulting videoPlayer on this page: demo of the videoPlayer - part 3.
What will we do in our third part ? :
The goal is to display a control bar when the mouse is hover the video or when the media is paused.
The control bar will look like this :
First we create the gray rectangle which is behind the meta-information and the two differents text. We will add them to a group to ease the use of trigger to show/hide them.
/** ########################################### * The control bar * ########################################### */ var controlBarBackGroundRect : Rectangle = Rectangle{ x:x y: vidHeight + y - 40 width:vidWith height: 40 opacity:0.5 fill:Color.DARKGRAY } var playPauseGroupWidth = 20 ; var pauseGroup : Group = Group{content:[]}; var playGroup : Group = Group{content:[]}; var progressNode = Group{content:[]}; var groupControlBar: Group = Group{ content: [controlBarBackGroundRect,progressNode, pauseGroup, playGroup] cursor: Cursor.DEFAULT opacity: 0.0 }
Don't forget to add it in the returned Group of our component:
return Group{ content: [mediaView,groupVidMETA, groupControlBar] }
What you can notice is that we have already prepare the differents things which will be putted in : the playButton, the pauseButton, the progressNode and the grayRectangle which is the bacground ofthe controlbar.
To display the progress of the media we will create a Progress's node. To create this progress Node we will uses the james weaver's how-to which is on this page : Progress Indicator: Creating a JavaFX Custom Node and Binding to a Model but we will customize it a little to fits our specifications.
Here is a summary of the changes :
And here is the code :
/* * ProgressNode.fx - * A custom node that functions as a progress bar * TODO: Add the ability to have an "infinite progress" look as well * * Developed 2008 by James L. Weaver (jim.weaver at lat-inc.com) * to demonstrate how to create custom nodes in JavaFX * * Used and changed 2008 by Jonathan ANTOINE to use in his videoPlayer */ package fr.antoinej.tools; import javafx.scene.CustomNode; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.paint.Color; import javafx.scene.paint.LinearGradient; import javafx.scene.paint.Paint; import javafx.scene.paint.Stop; import javafx.scene.shape.Rectangle; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.scene.text.Text; import javafx.scene.text.TextOrigin; public class ProgressNode extends CustomNode { /* * A number from 0.0 to 1.0 that indicates the amount of progress */ public var progress:Number; /* * The fill of the progress part of the progress bar. Because * this is of type Paint, a Color or gradient may be used. */ public-init var progressFill:Paint = LinearGradient { startX: 0.0 startY: 0.0 endX: 0.0 endY: 1.0 stops: [ Stop { offset: 0.0 color: Color.rgb(0, 192, 255) }, Stop { offset: 0.20 color: Color.rgb(0, 172, 234) }, Stop { offset: 1.0 color: Color.rgb(0, 112, 174) }, ] }; /* * The fill of the bar part of the progress bar. Because * this is of type Paint, a Color or gradient may be used. */ public-init var barFill:Paint = LinearGradient { startX: 0.0 startY: 0.0 endX: 0.0 endY: 1.0 stops: [ Stop { offset: 0.0 color: Color.rgb(112, 112, 112) }, Stop { offset: 1.0 color: Color.rgb(88, 88, 88) }, ] }; /* * The color of the progress percent text on the progress bar */ public-init var progressPercentColor:Color = Color.rgb(191, 223, 239); /* * The color of the progress text on the right side of the progress bar */ public-init var progressTextColor:Color = Color.WHITE; /* * Determines the width, in pixels, of the progress bar */ public-init var width:Integer = 200; public var leftStr : String = ""; public var rightStr : String = ""; public var centerStr : String = ""; /* * Determines the height, in pixels, of the progress bar */ public-init var height:Integer = 20; /** * Create the Node */ public override function create():Node { var textRef:Text; var progBarFont =Font.font("Verdana", FontWeight.EXTRA_BOLD, 10); var leftAndRightFont =Font.font("Sans serif", FontWeight.EXTRA_BOLD, 10); return Group{ content: [ // The entire progress bar Rectangle { x:0 y:0 width: bind width height: bind height fill: bind barFill }, Rectangle { x:0 y:0 width: bind progress * width height: height fill: bind progressFill } , // The percent complete displayed on the progress bar textRef = Text { translateX: width / 2 - 20 translateY: 5 textOrigin: TextOrigin.TOP font: progBarFont fill: bind progressPercentColor content: bind "{centerStr}({progress * 100 as Integer}%)" }, Text { translateX: bind width - rightStr.length() * 6 translateY: 5 textOrigin: TextOrigin.TOP font: leftAndRightFont fill: bind progressPercentColor content: bind "{rightStr}" }, Text { translateX: 4 translateY: 5 textOrigin: TextOrigin.TOP font: leftAndRightFont fill: bind progressPercentColor content: bind "{leftStr}" } ] } } }
Now we gonna add it to our videoPlayer changing the precedent progressNode declaration by this one :
var progressNode: ProgressNode = ProgressNode{ translateX:controlBarBackGroundRect.x + 10 + playPauseGroupWidth translateY:controlBarBackGroundRect.y + 10 progress: bind mediaPlayer.currentTime.toMillis() / media.duration.toMillis() centerStr: bind "{%tM mediaPlayer.currentTime.toDate()}:{%tS mediaPlayer.currentTime.toDate()}" height:20 width: vidWith - 20 - playPauseGroupWidth leftStr:"0:00${media.duration}" rightStr: bind "{%tM media.duration.toDate()}:{%tS media.duration.toDate()}" };
Note that we bind the rightStr to fit the sun usages given on this page.
Now we gonna add a play/pause button. To do this we are going to create two groups, a play group which will be visible when the video is paused and a pause group visible when the video is played.
To create this buttons we are using polygons whe re some may prefers image: that's our choice !
Here is the code :
var pauseGroup : Group = Group{ var rect1 =Rectangle { x: controlBarBackGroundRect.x + 7 y: controlBarBackGroundRect.y + 13 width: 4 height: 13 fill: Color.WHITE } ; var rect2 = Rectangle { x: controlBarBackGroundRect.x + 14 y: controlBarBackGroundRect.y + 13 width: 4 height: 13 fill: Color.WHITE }; content: [rect1,rect2 , Rectangle { x: controlBarBackGroundRect.x + 4 y: controlBarBackGroundRect.y + 11 width: 17 height: 16 fill: Color.TRANSPARENT } ] cursor:Cursor.HAND //On left click, play or pause the video onMouseClicked: function(me: MouseEvent){ //Only a left click play/pause the video if(me.button == MouseButton.PRIMARY) { mediaPlayer.pause(); } } onMouseEntered: function(me: MouseEvent){ Timeline { keyFrames: [ at(0s) { rect1.fill => rect1.fill tween Interpolator.LINEAR; rect2.fill => rect1.fill tween Interpolator.LINEAR; } at(800ms) { rect1.fill => Color.RED tween Interpolator.LINEAR; rect2.fill => Color.RED tween Interpolator.LINEAR; } ] }.play(); } onMouseExited: function(me: MouseEvent){ Timeline { keyFrames: [ at(0s) { rect1.fill => rect1.fill tween Interpolator.LINEAR; rect2.fill => rect1.fill tween Interpolator.LINEAR; } at(800ms) { rect1.fill => Color.WHITE tween Interpolator.LINEAR; rect2.fill => Color.WHITE tween Interpolator.LINEAR; } ] }.play(); } visible: bind (mediaPlayer.status == mediaPlayer.PLAYING) } var playGroup : Group = Group{ var polygon = Polygon { points: [ controlBarBackGroundRect.x + 7,controlBarBackGroundRect.y + 14 , controlBarBackGroundRect.x + 7,controlBarBackGroundRect.y + 27, controlBarBackGroundRect.x + 20,controlBarBackGroundRect.y + 20 ] fill: Color.WHITE }; content: [polygon , Rectangle { x: controlBarBackGroundRect.x + 4 y: controlBarBackGroundRect.y + 11 width: 17 height: 16 fill: Color.TRANSPARENT } ] cursor:Cursor.HAND //On left click, play or pause the video onMouseClicked: function(me: MouseEvent){ //Only a left click play/pause the video if(me.button == MouseButton.PRIMARY) { mediaPlayer.play(); } } onMouseEntered: function(me: MouseEvent){ Timeline { keyFrames: [ at(0s) { polygon.fill => polygon.fill tween Interpolator.LINEAR; } at(800ms) { polygon.fill => Color.RED tween Interpolator.LINEAR; } ] }.play(); } onMouseExited: function(me: MouseEvent){ Timeline { keyFrames: [ at(0s) { polygon.fill => polygon.fill tween Interpolator.LINEAR; } at(800ms) { polygon.fill => Color.WHITE tween Interpolator.LINEAR; } ] }.play(); } visible: bind (mediaPlayer.status == mediaPlayer.PAUSED) }
You can find our component source in this post and view a demo on this page: demo of the videoPlayer - part 3.
I am currently confronted to this problem: detect double clic in JavaFX.
When you make a double click, you click twice, so this is what will occurs:
So what I want is :
In swing this is done by using timer. So why not do the same with a Timeline in JavaFX.
This is how I solve the problem (the code is also linked to this post) :
/* * Main.fx * * Created on 14 déc. 2008, 15:10:59 */ package fr.antoinj.detectdoubleclick; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.scene.Group; import javafx.scene.input.MouseEvent; import javafx.scene.paint.Color; import javafx.scene.Scene; import javafx.scene.shape.Rectangle; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.scene.text.Text; import javafx.stage.Stage; /** * @author Jonathan */ var textContent = "Nothing detected"; var clickInTheLastFewMoments: Boolean = false; var lastLauchedTimeLine:Timeline ; var clickedTimeLine: Timeline= Timeline { keyFrames: [KeyFrame { time: 0s action:function(){ lastLauchedTimeLine.stop(); clickInTheLastFewMoments=true; } } , KeyFrame { time: 200ms action:function(){ clickInTheLastFewMoments=false; simpleClick(); } } ] } function simpleClick(){ textContent="Simple click detected"; } function doubleClick(){ textContent="Double click detected"; } Stage { title: "How I detect doubleClick in JavaFX" width: 400 height: 80 scene: Scene { content: Group{ content: [Rectangle { x: 75, y: 0 width: 400, height: 50 fill: Color.BLACK onMouseClicked: function(me:MouseEvent){ if(clickInTheLastFewMoments){ clickInTheLastFewMoments=false; clickedTimeLine.stop(); doubleClick(); }else { clickedTimeLine.playFromStart(); } } }, Rectangle { x: 0, y: 0 width: 75 height: 50 fill: Color.ORANGERED onMouseClicked: function(me:MouseEvent){ textContent="Nothing detected"; } }, Text { font: Font.font("Verdana",FontWeight.BOLD,12) fill: Color.BLACK x: 8 y: 25 content: "REFRESH" } , Text { font: Font.font("Verdana",FontWeight.MEDIUM,24) fill:Color.PINK x: 80 y: 30 content: bind textContent; }] } } }
You can also see a demo of this javaFx apps here : link to the demo page or launch this JNLP : link to the JNLP
My question is : Do you have a better way to do this ?
Let's continue pur story about creating our own media player
What will we do in our second part ? :
We already have all the informations to display because we gived them to our component.
Now we will display them in an header:
First we create the gray rectangle which is behind the meta-information and the two differents text. We will add them to a group to ease the use of trigger to show/hide them.
/** ########################################### * The Top Rectangle of description * ########################################### */ var topRDescription : Rectangle = Rectangle{ x:x y:y width:vidWith height: 40 opacity:0.5 fill:Color.DARKGRAY } var titleText: Text = Text{ x:topRDescription.x + 10 y: topRDescription.y + 25 textOrigin: TextOrigin.BOTTOM content: bind vidTitle fill:Color.ORANGE font: Font.font("Verdana", FontWeight.EXTRA_BOLD, 18) } var descriptionText: Text = Text{ x:topRDescription.x + 10 y: topRDescription.y + 35 content: bind vidDescription fill:Color.WHITE font: Font.font("Verdana", FontWeight.MEDIUM, 10) } var groupVidMETA : Group = Group{ content: [topRDescription,titleText,descriptionText] cursor: Cursor.DEFAULT opacity: 0.0 visible: bind showInfoBar }
Don't forget to add it in the returned Group of our component:
return Group{ content: [mediaView,groupVidMETA] }
You can see that the visibility of our meta-information's bar is bind to a var showInfoBar. This is on this var that we will act to show/hide the bar.
Sets this var to true and the informations will be displayed all the time... this is cool but quite boring when you want watch the movie which is behind. We will so add some animations to display it in a non disturbing way.
We will now display the bar only when the mouse is hover the video or when the video is paused.
To do this we will use the opacity of the bar and add animation in the MediaView's node triggers. Remember the MediaView is the node in which the media is displayed.
Resume of the things to do :
The resulting mediaView var is then :
//The node displaying the video var mediaView : MediaView = MediaView{ x:x y:y mediaPlayer: bind mediaPlayer fitHeight:vidHeight fitWidth:vidWith //Show the informations in an animation. onMouseEntered: function(me: MouseEvent){ Timeline { keyFrames: [ at(0s) { groupVidMETA.opacity => groupVidMETA.opacity tween Interpolator.LINEAR; groupControlBar.opacity => groupControlBar.opacity tween Interpolator.LINEAR; } at(500ms) { groupVidMETA.opacity => 1.0 tween Interpolator.LINEAR; groupControlBar.opacity => 1.0 tween Interpolator.LINEAR; } ] }.play() } //Hide the informations in an animation if the video is not paused. onMouseExited: function(me: MouseEvent){ if(mediaPlayer.status != MediaPlayer.PAUSED){ Timeline { keyFrames: [ at(0s) { groupVidMETA.opacity => groupVidMETA.opacity tween Interpolator.LINEAR; } at(500ms) { groupVidMETA.opacity => 0.0 tween Interpolator.LINEAR; } ] }.play(); } } };
We are abble to know if the video is paused by accessing the mediaPlayer's status and comparing it to it's PAUSED def.
Now that informations are displayed, we will add triggers to play or pause the video.
This is done by adding a onMouseClicked trigger to our mediaView Component. We will also check that it's the left button that is clicked to perform the play/pause and reserve the right button to maybe future functionnality.
Also we will set the cursor to the HAND def: tell our watcher that they can do something by clicking the video.
This is what we add to our mediaView component:
cursor:Cursor.HAND onMouseClicked: function(me: MouseEvent){ //Only a left click play/pause the video if(me.button == MouseButton.PRIMARY ) { if(mediaPlayer.status == mediaPlayer.PAUSED){ mediaPlayer.play(); }else { mediaPlayer.pause(); } } }
You can find our component source in this post and view a demo on this page: demo of the videoPlayer - part 2.
It can be long to load... My server fault ...
hey,
In my first try to create a videoPlayer I always get an exception trying to play a media which are on my local hard disk.
I was trying to execute this media :
var mediaURL : String = "file://C:/tchou.flv";
But it generates me logs like this one :
FX Media Object caught Exception com.sun.media.jmc.MediaUnavailableException: Media unavailable:
I search on the web and I notice that I was not alone facing this problem : look at this post.
But nobody seems to find the answer to this problem...
Actually I finally manage to get ride of this exception and it was quite simple : we should use '/' instead of '//' when using the "file" protocol. You just have to use the code below and it will work :
/* * Do not use this : * var mediaURL : String = "file://C:/tchou.flv"; */ var mediaURL : String = "file:/C:/tchou.flv";
Quite easy in fact !
I noticed in this post that the mediaComponent pointed out in the JavaFX demos was not still available and that we will so make our own...
Lets start !
What will we do in our first part ? :
The first thing to do is quite easy : create a new JavaFX class in Nebeans and make it extends the CustomNode class from the javafx.scene package.
This class is provided to enable you making your own nodes.
You will then have to ovveride the "create():Node" method :
public override function create():Node{ return Group{ content: [] } }
We will add more things in the content when we will build them.
First we will add some var to our class, They will contains some informations about our component :
/** X attribute of our component. */ public var x : Integer = 0 ; /** Y attribute of our component. */ public var y : Integer = 0 ; /** A description of the video. */ public var vidDescription : String = "" ; /** The title of the video. */ public var vidTitle : String = "" ; /** Start volume of the video. */ public var startVolume : Number = 0.5; /** Do where display the meta informations on the video */ public var showInfoBar : Boolean = true; /** The url of the video. */ public var mediaURL : String ="" ; /** The Width in wich where will make the video fit. */ public var vidWith : Integer = 384; /** The height in wich where will make the video fit. */ public var vidHeight : Integer = 288 ;
We will add the video in our component. To add this features we will use three differents Object in the JavaFX API:
Lets add it in our class and use the differents var we created just before :
var media:Media = Media{ source: mediaURL }; var mediaPlayer: MediaPlayer = MediaPlayer{ media:media autoPlay:true }; var mediaView : MediaView = MediaView{ x:x y:y mediaPlayer: bind mediaPlayer fitHeight:vidHeight fitWidth:vidWith };
The autoPlay attribute tells that the video is played as soon as it can be by the player (when enough data is available when buffered).
Now that we have created the different component we will add the mediaView in the group returned by our create() function:
public override function create():Node{ return Group{ content: [mediaView ] } }
Now by adding our component in your application you will display the video and play it. This is a first step in the birth of our videoComponent:
In the next part you will learn how to display the informations on the video currently played when hovering the video and how to add some triggers (play/pause the video by clicking on it).
+++
Have you been trying all the wonderful demo on on the JavaFX web site ? Yes ? Me too !
You can try the "Simple Video Player" for example :
It's great but when I tried to reproduce the video player i meet a little problem.
It tells "You could use the JavaFX Media Component, a prefabricated video player in the com.sun.fxmediacomponent package."... But where is it ? I Just cannot find it on the web...
You then have two solutions:
I really prefere the second solutions and I will make a little How-To in a future posts to tell you how to do...
++
« previous entries - page 3 of 4 - next entries »