Moving an ImageView located in a WindowManager doesn't work properly - android-layout

I'm trying to draw an Icon over everything on the screen (TOP MOST) similar to the chathead of new Facebook messenger
I have create a service to work in the background and based on a specific condition my icon should appear on the screen (exactly like when someone sends you a message on facebook the messenger service will hook the message and shows the chathead on the screen to notify you about the new message)
What I did:
I have created the service and gave it the permission to show system alert windows (since the head is actually a system alert window)
[assembly: UsesPermission(Name = Android.Manifest.Permission.SystemAlertWindow)]
I have inherited a class (StickyHeadView) from ImageView and implemented OnTouchListener listener using the following way :
class StickyHeadView : ImageView, Android.Views.View.IOnTouchListener
{
private StickyHeadService OwnerService;
public StickyHeadView(StickyHeadService ContextService, Context context)
: base(context)
{
OwnerService = ContextService;
SetOnTouchListener(this);
}
float TouchMoveX;
float TouchMoveY;
public bool OnTouch(View v, MotionEvent e)
{
var windowService = OwnerService.GetSystemService(Android.Content.Context.WindowService);
var windowManager = windowService.JavaCast<Android.Views.IWindowManager>();
switch (e.Action & e.ActionMasked)
{
case MotionEventActions.Move:
TouchMoveX = (int)e.GetX();
TouchMoveY = (int)e.GetY();
OwnerService.LOParams.X = (int)(TouchMoveX);
OwnerService.LOParams.Y = (int)(TouchMoveY);
windowManager.UpdateViewLayout(this, OwnerService.LOParams);
Log.Debug("Point : ", "X: " + Convert.ToString(OwnerService.LOParams.X) + " Y: " + Convert.ToString(OwnerService.LOParams.Y));
return true;
case MotionEventActions.Down:
return true;
case MotionEventActions.Up:
return true;
}
return false;
}
}
The service has wiindow manager to show the Icon on it...in Service "OnStart" event I initialize the Head :
private StickyHeadView MyHead;
public Android.Views.WindowManagerLayoutParams LOParams;
public override void OnStart(Android.Content.Intent intent, int startId)
{
base.OnStart(intent, startId);
var windowService = this.GetSystemService(Android.Content.Context.WindowService);
var windowManager = windowService.JavaCast<Android.Views.IWindowManager>();
MyHead = new StickyHeadView(this, this);
MyHead.SetImageResource(Resource.Drawable.Icon);
LOParams = new Android.Views.WindowManagerLayoutParams(Android.Views.WindowManagerLayoutParams.WrapContent,
Android.Views.WindowManagerLayoutParams.WrapContent,
Android.Views.WindowManagerTypes.Phone,
Android.Views.WindowManagerFlags.NotFocusable,
Android.Graphics.Format.Translucent);
LOParams.Gravity = GravityFlags.Top | GravityFlags.Left;
LOParams.X = 10;
LOParams.Y = 10;
windowManager.AddView(MyHead, LOParams);
}
as you can see I have declared a WindowManager and added the view (MyHead) to it with special parameters
My Problem :
When ever I try to move the View (My head) it doesn't move in a stable way and keeps having a quake!
I'm testing it using android 4.0.4 on real HTC Phone
I'm using monodroid
Please help...if the implementation of the touch is not right please suggest a better way...thank you.

In your code just use...
TYPE_SYSTEM_ALERT
or
TYPE_PHONE
instead of
TYPE_SYSTEM_OVERLAY
Hope this will help you.
a working example:
#Override
public void onCreate() {
super.onCreate();
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
chatHead = new ImageView(this);
chatHead.setImageResource(R.drawable.ic_launcher);
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, //TYPE_PHONE
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = 0;
params.y = 100;
windowManager.addView(chatHead, params);
chatHead.setOnTouchListener(new View.OnTouchListener() {
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
#Override public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
initialX = params.x;
initialY = params.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
return true;
case MotionEvent.ACTION_UP:
return true;
case MotionEvent.ACTION_MOVE:
params.x = initialX + (int) (event.getRawX() - initialTouchX);
params.y = initialY + (int) (event.getRawY() - initialTouchY);
windowManager.updateViewLayout(chatHead, params);
return true;
}
return false;
}
});
}

The e.GetX()/eGetY() you are using is relative to view position so when you move the view with UpdateViewLayout the next values will be relative to the move. It works using GetRawX()/GetRawY(), but you have to keep track of the initial Down rawX and rawY also.
Here is my JAVA that works:
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
layoutParams.x = Math.round(event.getRawX() - downX);
layoutParams.y = Math.round(event.getRawY() - downY);
windowManager.updateViewLayout(floatingView, layoutParams);
return true;
case MotionEvent.ACTION_DOWN:
downX = event.getRawX() - layoutParams.x;
downY = event.getRawY() - layoutParams.y;
return true;
case MotionEvent.ACTION_UP:
return true;
}
return false;
}
One comment, there's a big downside in using windowManager.updateViewLayout(...) this method will call onLayout on the floating view for each move, and that might be a performance issue, anyway until now I haven't found another method to move the floating view.

Try this might be help ful
first add global variable on your activity:
WindowManager wm;
LinearLayout lay;
float downX,downY;
after put in code to oncreate on your activity
Button btnstart=(Button)findViewById(R.id.button1);
Button btnstop=(Button)findViewById(R.id.button2);
btnstart.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(lay==null)
{
wm = (WindowManager) getApplicationContext().getSystemService(
Context.WINDOW_SERVICE);
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
| WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.x = (int) wm.getDefaultDisplay().getWidth();
params.y = 0;
// params.height = wm.getDefaultDisplay().getHeight()/2;
params.width = LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.gravity = Gravity.TOP | Gravity.LEFT;
params.setTitle("Info");
lay = null;
lay = new LinearLayout(getApplicationContext());
lay.setOrientation(LinearLayout.VERTICAL);
// lay.setAlpha(0.5f);
TextView txt_no = new TextView(getApplicationContext());
txt_no.setTextSize(10.0f);
txt_no.setText("Moving view by stack user!");
txt_no.setTextColor(Color.BLACK);
// txt_no.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
// LayoutParams.WRAP_CONTENT));
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(0, 0, 0, 0); // margins as you wish
txt_no.setGravity(Gravity.RIGHT);
txt_no.setBackgroundColor(Color.WHITE);
txt_no.setLayoutParams(layoutParams);
txt_no.setPadding(10, 10, 10, 10);
lay.addView(txt_no);
AlphaAnimation alpha = new AlphaAnimation(0.5F, 0.5F);
alpha.setDuration(0); // Make animation instant
alpha.setFillAfter(true); // Tell it to persist after the animation ends
// And then on your layout
wm.addView(lay, params);
txt_no.startAnimation(alpha);
downX=params.x;
downY=params.y;
Log.v("MSES>", "x="+ downX +",y="+ downY);
lay.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
params.x = Math.round(event.getRawX() - downX);
params.y = Math.round(event.getRawY() - downY);
wm.updateViewLayout(lay, params);
Log.v("MSES EVENT>", "x="+ event.getRawX() +",y="+ event.getRawY());
Log.v("MSES MOVE>", "x="+ params.x +",y="+ params.y);
return true;
case MotionEvent.ACTION_DOWN:
downX = event.getRawX() - params.x;
downY = event.getRawY() - params.y;
Log.v("MSES DOWN>", "x="+ params.x +",y="+ params.y);
return true;
case MotionEvent.ACTION_UP:
//params.x = Math.round(event.getRawX() - downX);
//params.y = Math.round(event.getRawY() - downY);
//wm.updateViewLayout(lay, params);
return true;
}
return false;
}
});
}
}
});
btnstop.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (lay != null) {
lay.removeAllViews();
wm.removeViewImmediate(lay);
lay = null;
}
}
});

Related

AVCaptureSession captured photo's size different than the preview?

EDIT:: Reading through this again now realize it isn’t very clear as to how the pictures were captured, how they’re being displayed, and why/what makes them different.
To that end— We are using AVCapture library to manually take photos within our app. We have a preview display in the app so the user can see what the image they’re taking looks like, just how any standard photo app these days does it. So what these two images are showing are the preview of the image on the screen, before the image is captured and the captured image. This was done by taking a screenshot of the preview and then a screenshot of the resulting capture image.
All this to say the captured image appears to be returned with differing dimensions or scaling attributes. We are displaying the preview and the resulting captures using a native iOS preview view and a Xamarin.Image respectively.
Below details our attempts at addressing the issue by changing sizing, layering, and stretching attributes to no avail.
To that end we’ve created a support ticket with MSFT regarding this issue.
These two images are the camera preview and the resulting capture (in that order, respectively [taken via screenshots]). We want the captured photo to match the preview/vice versa. How can we address this?
Tried manipulating the CALayer containing the photo data to size the image like how a Xamarin.Forms' image sizes with AspectFit by assigning the ContentsGravity with various options like kCAGravityResizeAspect. Fiddled with other Contents options such as ContentsRect and ContentsScale but no dice. Below is the View and its corresponding Renderer. So how to address the sizing issue?
Native Camera View
namespace App.iOS.Views
{
public class NativeCameraView : UIView
{
AVCaptureVideoPreviewLayer previewLayer;
CameraOptions cameraOptions;
public AVCaptureSession CaptureSession { get; private set; }
public AVCaptureStillImageOutput CaptureOutput { get; set; }
public bool IsPreviewing { get; set; }
public NativeCameraPreview(CameraOptions options)
{
cameraOptions = options;
IsPreviewing = false;
Initialize();
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
UIDevice device = UIDevice.CurrentDevice;
UIDeviceOrientation orientation = device.Orientation;
AVCaptureConnection previewLayerConnection = this.previewLayer.Connection;
if (previewLayerConnection.SupportsVideoOrientation)
{
switch (orientation)
{
case UIDeviceOrientation.Portrait:
UpdatePreviewLayer(previewLayerConnection,
AVCaptureVideoOrientation.Portrait);
break;
case UIDeviceOrientation.LandscapeRight:
UpdatePreviewLayer(previewLayerConnection,
AVCaptureVideoOrientation.LandscapeLeft);
break;
case UIDeviceOrientation.LandscapeLeft:
UpdatePreviewLayer(previewLayerConnection,
AVCaptureVideoOrientation.LandscapeRight);
break;
case UIDeviceOrientation.PortraitUpsideDown:
UpdatePreviewLayer(previewLayerConnection,
AVCaptureVideoOrientation.PortraitUpsideDown);
break;
default:
UpdatePreviewLayer(previewLayerConnection,
AVCaptureVideoOrientation.Portrait);
break;
}
}
}
private void UpdatePreviewLayer(AVCaptureConnection layer,
AVCaptureVideoOrientation orientation)
{
layer.VideoOrientation = orientation;
previewLayer.Frame = this.Bounds;
}
public async Task CapturePhoto()
{
var videoConnection = CaptureOutput.ConnectionFromMediaType(AVMediaType.Video);
var sampleBuffer = await CaptureOutput.CaptureStillImageTaskAsync(videoConnection);
var jpegData = AVCaptureStillImageOutput.JpegStillToNSData(sampleBuffer);
var photo = new UIImage(jpegData);
var rotatedPhoto = RotateImage(photo, 180f);
CALayer layer = new CALayer
{
//ContentsGravity = "kCAGravityResizeAspect",
//ContentsRect = rect,
//GeometryFlipped = true,
ContentsScale = 1.0f,
Frame = Bounds,
Contents = rotatedPhoto.CGImage //Contents = photo.CGImage,
};
MainPage.UpdateSource(UIImageFromLayer(layer).AsJPEG().AsStream());
MainPage.UpdateImage(UIImageFromLayer(layer).AsJPEG().AsStream());
}
public UIImage RotateImage(UIImage image, float degree)
{
float Radians = degree * (float)Math.PI / 180;
UIView view = new UIView(frame: new CGRect(0, 0, image.Size.Width, image.Size.Height));
CGAffineTransform t = CGAffineTransform.MakeRotation(Radians);
view.Transform = t;
CGSize size = view.Frame.Size;
UIGraphics.BeginImageContext(size);
CGContext context = UIGraphics.GetCurrentContext();
context.TranslateCTM(size.Width / 2, size.Height / 2);
context.RotateCTM(Radians);
context.ScaleCTM(1, -1);
context.DrawImage(new CGRect(-image.Size.Width / 2, -image.Size.Height / 2, image.Size.Width, image.Size.Height), image.CGImage);
UIImage imageCopy = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return imageCopy;
}
UIImage ImageFromLayer(CALayer layer)
{
UIGraphics.BeginImageContextWithOptions(
layer.Frame.Size,
layer.Opaque,
0);
layer.RenderInContext(UIGraphics.GetCurrentContext());
var outputImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return outputImage;
}
void Initialize()
{
CaptureSession = new AVCaptureSession();
CaptureSession.SessionPreset = AVCaptureSession.PresetPhoto;
previewLayer = new AVCaptureVideoPreviewLayer(CaptureSession)
{
Frame = Bounds,
VideoGravity = AVLayerVideoGravity.ResizeAspectFill
};
var videoDevices = AVCaptureDevice.DevicesWithMediaType(AVMediaType.Video);
var cameraPosition = (cameraOptions == CameraOptions.Front) ? AVCaptureDevicePosition.Front : AVCaptureDevicePosition.Back;
var device = videoDevices.FirstOrDefault(d => d.Position == cameraPosition);
if (device == null)
{
return;
}
NSError error;
var input = new AVCaptureDeviceInput(device, out error);
var dictionary = new NSMutableDictionary();
dictionary[AVVideo.CodecKey] = new NSNumber((int)AVVideoCodec.JPEG);
CaptureOutput = new AVCaptureStillImageOutput()
{
OutputSettings = new NSDictionary()
};
CaptureSession.AddOutput(CaptureOutput);
CaptureSession.AddInput(input);
Layer.AddSublayer(previewLayer);
CaptureSession.StartRunning();
IsPreviewing = true;
}
}
}
Native Camera Renderer
[assembly: ExportRenderer(typeof(CameraView), typeof(CameraViewRenderer))]
namespace App.iOS.Renderers
{
public class CameraViewRenderer : ViewRenderer<CameraView, NativeCameraView>
{
NativeCameraView uiCameraView;
protected override void OnElementChanged(ElementChangedEventArgs<CameraView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
uiCameraView = new NativeCameraView(e.NewElement.Camera);
SetNativeControl(uiCameraView);
}
if (e.OldElement != null)
{
// Unsubscribe
uiCameraView.Tapped -= OnCameraViewTapped;
}
if (e.NewElement != null)
{
// Subscribe
uiCameraView.Tapped += OnCameraViewTapped;
}
}
async void OnCameraViewTapped(object sender, EventArgs e)
{
await uiCameraView.CapturePhoto();
}
}
}
NOTE A similar question appears to have been asked quite some time ago.

Radio Button in Xamarin.iOS listen for tap?

I need to get a view with two radio buttons working, where only one can be clicked at a time. Using the answer posted here by user Alanc Liu: Radio button in xamarin.ios I've got my View Controller looking correct, but I can't figure out how to listen for the tap to set the other radio button to false.
I've tried playing around with adding a gesture recognizer to the ViewDidLoad method, but haven't gotten anything to work yet (I've mostly just used the storyboard previously to add methods to button clicks).
My View Controller:
public partial class VerifyViewController : UIViewController
{
public VerifyViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
MyRadioButton tBtn = new MyRadioButton(new CGPoint(100, 300), "TEXT PHONE");
MyRadioButton eBtn = new MyRadioButton(new CGPoint(100, 375), "EMAIL");
this.Add(tBtn);
this.Add(eBtn);
}
}
And his Radio Button Classes:
public class MyRadioButton : UIView
{
private CircleView circleView;
private UILabel lbTitle;
public bool State {
get {
return circleView.State;
}
set {
circleView.State = value;
}
}
public MyRadioButton (CGPoint pt,string title)
{
this.Frame = new CGRect (pt, new CGSize (150, 30));
circleView = new CircleView (new CGRect(0, 0, 30, 30));
lbTitle = new UILabel (new CGRect (30, 0, 120, 30));
lbTitle.Text = title;
lbTitle.TextAlignment = UITextAlignment.Center;
this.AddSubview (circleView);
this.AddSubview (lbTitle);
this.BackgroundColor = UIColor.FromRGBA(1,0,0,0.3f);
UITapGestureRecognizer tapGR = new UITapGestureRecognizer (() => {
State = !State;
});
this.AddGestureRecognizer (tapGR);
}
}
class CircleView : UIView
{
private bool state = false;
public bool State {
get {
return state;
}
set {
state = value;
this.SetNeedsDisplay ();
}
}
public CircleView (CGRect frame)
{
this.BackgroundColor = UIColor.Clear;
this.Frame = frame;
}
public override void Draw (CoreGraphics.CGRect rect)
{
CGContext con = UIGraphics.GetCurrentContext ();
float padding = 5;
con.AddEllipseInRect (new CGRect (padding, padding, rect.Width - 2 * padding, rect.Height - 2 * padding));
con.StrokePath ();
if (state) {
float insidePadding = 8;
con.AddEllipseInRect (new CGRect (insidePadding, insidePadding, rect.Width - 2 * insidePadding, rect.Height - 2 * insidePadding));
con.FillPath ();
}
}
}
Expose a public event in MyRadioButton ,call it when we tap the radio button.
Code in MyRadioButton:
//define the event inside MyRadioButton
public delegate void TapHandler(MyRadioButton sender);
public event TapHandler Tap;
//call it in MyRadioButton(CGPoint pt, string title)
UITapGestureRecognizer tapGR = new UITapGestureRecognizer(() => {
State = !State;
Tap(this);
});
Handle the event inside your viewController
Code in ViewController
MyRadioButton tBtn = new MyRadioButton(new CGPoint(100, 300), "TEXT PHONE");
MyRadioButton eBtn = new MyRadioButton(new CGPoint(100, 375), "EMAIL");
this.Add(tBtn);
this.Add(eBtn);
tBtn.Tap += Btn_Tap;
eBtn.Tap += Btn_Tap;
// set the default selection
Btn_Tap(tBtn);
MyRadioButton PreviousButton;
private void Btn_Tap(MyRadioButton sender)
{
if(PreviousButton != null)
{
//set previous to false
PreviousButton.State = false;
}
//set current to true
sender.State = true;
//assign current to previous
PreviousButton = sender;
}
Result:

bluetooth controller not working after sending few messages

I have a simple bluetooth code to activate a relay.
it is from an old project and it used to work fine, but I was sending just bit in the past, this time I have to send strings.
With this code I can send six command and then the device doesn't respond anymore. when I stop the application suddenly all the commands "un-executed" before are executed by the relay board. If I change the size of the buffer it seems to have some effect on the number of messages I can send.
I am using an old phone a controller with Android 2 and API 10
Any idea where I should investigate?
Thanks
public class Menuuu extends Activity {
BluetoothAdapter mBluetoothAdapter;
BluetoothSocket mmSocket;
BluetoothDevice mmDevice;
OutputStream mmOutputStream;
InputStream mmInputStream;
String a;
Editable b;
final byte[] buffer = new byte[2048];
private ToggleButton T1;
private static final String TAG = "MyActivity";
public void write_buf(byte[] buffer) {
try {
mmOutputStream.write(buffer, 0, 1024 );
mmOutputStream.flush();
} catch (IOException e) { }
}
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final ToggleButton T1 = (ToggleButton) findViewById(R.id.Button1);
T1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (T1.isChecked()) {
// TODO Auto-generated method stub
buffer[0] = 'r';
buffer[1] = 'e';
buffer[2] = 'l';
buffer[3] = 'a';
buffer[4] = 'y';
buffer[5] = ' ';
buffer[6] = 'o';
buffer[7] = 'n';
buffer[8] = ' ';
buffer[9] = '0';
buffer[10] = ' ';
buffer[11] = '\r';
buffer[12] = '\n';
}
if (!T1.isChecked()) {
// TODO Auto-generated method stub
buffer[0] = 'r';
buffer[1] = 'e';
buffer[2] = 'l';
buffer[3] = 'a';
buffer[4] = 'y';
buffer[5] = ' ';
buffer[6] = 'o';
buffer[7] = 'f';
buffer[8] = 'f';
buffer[9] = ' ';
buffer[10] = '0';
buffer[11] = '\r';
buffer[12] = '\n';
}
write_buf(buffer);
}
});
void findBT() throws IOException
{
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if(!mBluetoothAdapter.isEnabled())
{
Intent enableBluetooth = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBluetooth, 1);
}
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if(pairedDevices.size() > 0)
{
for(BluetoothDevice device : pairedDevices)
{
if(device.getName().equals(a))
{
mmDevice = device;
break;
}
}
}
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); //Standard //SerialPortService ID
mmSocket = mmDevice.createRfcommSocketToServiceRecord(uuid);
mmSocket.connect();
mmOutputStream = mmSocket.getOutputStream();
mmInputStream = mmSocket.getInputStream();
public boolean onCreateOptionsMenu(Menu menu){
super.onCreateOptionsMenu(menu);
MenuInflater h= getMenuInflater();
h.inflate(R.menu.hardmenu,menu);
return true;
}

Drag and drop JLabel while preserving the original JLabel

I am working on a GUI that displays an image of a floor plan (JLabel with ImageIcon) and a number of small icons (JLabels with ImageIcon) down the left hand side. The idea is to be able to select one of the icons and drag and drop it onto a position on the floor plan. My code is working fine except that when you drag an icon onto the floor plan, I need the original icon to remain in place, so it can be placed in several other positions on the floor plan if required. So I need to be able to clone the icon that I am moving when the mouse is pressed.
My code below shows a temp fix between the //******** markers but of course this only works correctly for the topmost icon. I need to somehow clone "Component comp" as a new JLabel.
Below is the relevant part of my code:
class MouseHandler extends MouseAdapter {
private Component dragComponent;
private Sidebar board;
private Point dragOffset;
public MouseHandler(Sidebar board) {
this.board = board;
}
public Sidebar getBoard() {
return board;
}
#Override
public void mousePressed(MouseEvent e) {
Component comp = getBoard().getComponentAt(e.getPoint());
if (comp != null) {
if (comp instanceof JLabel) {
//**************
String imagePath = "/Downlight 1.gif";
Image Images = new ImageIcon(this.getClass().getResource(imagePath)).getImage();
String path = "Images" + imagePath;
ImageIcon icon = new ImageIcon(path);
JLabel lbl = new JLabel(icon);
lbl.setBounds(22, 26, 36, 36);
lbl.setIcon(new ImageIcon(Images));
lbl.setOpaque(true);
Sidebar board = getBoard();
board.add(lbl, new Point(40, 44));
board.setComponentZOrder(lbl, 0);
//**************
dragComponent = comp;
dragOffset = new Point();
dragOffset.x = e.getPoint().x - comp.getX();
dragOffset.y = e.getPoint().y - comp.getY();
}
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (dragComponent != null) {
board = getBoard();
Point dragPoint = new Point();
dragPoint.x = e.getPoint().x - dragOffset.x;
dragPoint.y = e.getPoint().y - dragOffset.y;
dragComponent.setLocation(dragPoint);
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (dragComponent != null) {
dragComponent = null;
}
}
}
}
Thanks in advance for any help with this.
I found a solution that enabled me to copy the icon and bounds of the original JLabel to a new JLabel, which replaces the one being dragged:
#Override
public void mousePressed(MouseEvent e) {
Component comp = getBoard().getComponentAt(e.getPoint());
if (comp != null) {
if (comp instanceof JLabel) {
JLabel label = (JLabel) comp;
ImageIcon icon = ((ImageIcon)label.getIcon());
Rectangle rect = label.getBounds();
JLabel lbl = new JLabel(icon);
lbl.setBounds(rect);
lbl.setVisible(true);;
lbl.setOpaque(true);
Mainpanel board = getBoard();
board.add(lbl);
board.setComponentZOrder(lbl, 0);
dragComponent = comp;
dragOffset = new Point();
dragOffset.x = e.getPoint().x - comp.getX();
dragOffset.y = e.getPoint().y - comp.getY();
}
}
}

How to do off-screen drawing using Cairo in Gtk+?

I'm trying to do my drawing stuff in an Cairo Image Context. Is there a way to load the content of the Image context to a Cairo Context on expose event?
For example I want to draw a series of dots based on cursor movements over a drawing area, If I want to keep all the dots I should use an off-screen buffer, so I use an Image Context, but I cannot find a way to draw it to the Cairo Context on expose event...
any solution?
I found the solution myself!
here it is:
using Cairo;
using Gtk;
public class Canvas : Gtk.DrawingArea{
public Canvas(MainWindow mw){
stdout.printf("-> Canvas\n");
main_window = mw;
is_pressed_down = false;
add_events(Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.BUTTON_RELEASE_MASK |
Gdk.EventMask.POINTER_MOTION_MASK);
set_size_request(400, 300);
}
~Canvas(){
stdout.printf("<- Canvas\n");
}
public override void realize(){
base.realize();
stdout.printf("realize\n");
}
protected override bool configure_event(Gdk.EventConfigure event){
int x, y;
window.get_size(out x, out y);
offscreen_surface = new Cairo.ImageSurface(Cairo.Format.RGB24, x, y);
gc = new Cairo.Context(offscreen_surface);
gc.set_antialias(Antialias.NONE);
gc.set_line_width(1);
gc.set_source_rgb(1, 1, 1);
gc.paint(); // it will make trouble if user resize the window
string msg = "x: " + x.to_string() + ", y: " + y.to_string();
main_window.set_statusbar(msg);
return true;
}
protected override bool expose_event(Gdk.EventExpose event){
var tgc = Gdk.cairo_create(window); //!!!
tgc.set_source_rgb(1, 1, 1);
tgc.paint();
tgc.set_source_surface(offscreen_surface, 0, 0);
tgc.paint();
return true;
}
public override bool motion_notify_event(Gdk.EventMotion event)
{
string msg = "x: " + event.x.to_string() + ", y: " + event.y.to_string();
main_window.set_statusbar(msg);
if(is_pressed_down){
//gc.set_source_rgb(1, 1, 1);
//gc.paint();
gc.set_source_rgb(1, 0.5, 0);
//gc.move_to(event.x, event.x);
gc.arc(event.x, event.y, 1, 0, 360);
gc.stroke();
weak Gdk.Region region = this.window.get_clip_region();
this.window.invalidate_region(region, true);
this.window.process_updates(true);
}
return true;
}
public override bool button_press_event(Gdk.EventButton event)
{
stdout.printf("Canvas.button_press_event\n");
is_pressed_down = true;
return true;
}
public override bool button_release_event(Gdk.EventButton event)
{
stdout.printf("Canvas.button_release_event\n");
is_pressed_down = false;
return true;
}
public Cairo.Context get_context(){
return gc;
}
private Cairo.Context gc;
private weak MainWindow main_window;
private Cairo.ImageSurface offscreen_surface;
private bool is_pressed_down;
}

Resources