I need to present a WIC Bitmap from SharpDX in a WPF application. The WIC Bitmap inherits from BitmapSource, but it's not the same BitmapSource that WPF uses, though the class names are the same. How can I convert from one to another?
What you can do is create a custom derived class from WPF's BitmapSource.
For example, for this XAML:
<Window x:Class="SharpDXWpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Image Name="MyImage"></Image>
</Grid>
</Window>
This Window code uses a custom "WicBitmapSource".
public partial class MainWindow : Window
{
private WicBitmapSource _bmp;
public MainWindow()
{
InitializeComponent();
_bmp = new WicBitmapSource(#"c:\path\killroy_was_here.png");
MyImage.Source = _bmp;
}
protected override void OnClosed(EventArgs e)
{
_bmp.Dispose();
}
}
Here is some sample code for this SharpDX/Wic custom BitmapSource (some information are grabbed from here: https://blogs.msdn.microsoft.com/dwayneneed/2008/06/20/implementing-a-custom-bitmapsource/).
public class WicBitmapSource : System.Windows.Media.Imaging.BitmapSource, IDisposable
{
public WicBitmapSource(string filePath)
{
if (filePath == null)
throw new ArgumentNullException(nameof(filePath));
using (var fac = new ImagingFactory())
{
using (var dec = new SharpDX.WIC.BitmapDecoder(fac, filePath, DecodeOptions.CacheOnDemand))
{
Frame = dec.GetFrame(0);
}
}
}
public WicBitmapSource(BitmapFrameDecode frame)
{
if (frame == null)
throw new ArgumentNullException(nameof(frame));
Frame = frame;
}
public BitmapFrameDecode Frame { get; }
public override int PixelWidth => Frame.Size.Width;
public override int PixelHeight => Frame.Size.Height;
public override double Height => PixelHeight;
public override double Width => PixelWidth;
public override double DpiX
{
get
{
Frame.GetResolution(out double dpix, out double dpiy);
return dpix;
}
}
public override double DpiY
{
get
{
Frame.GetResolution(out double dpix, out double dpiy);
return dpiy;
}
}
public override System.Windows.Media.PixelFormat Format
{
get
{
// this is a hack as PixelFormat is not public...
// it would be better to do proper matching
var ct = typeof(System.Windows.Media.PixelFormat).GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new[] { typeof(Guid) },
null);
return (System.Windows.Media.PixelFormat)ct.Invoke(new object[] { Frame.PixelFormat });
}
}
// mostly for GIFs support (indexed palette of 256 colors)
public override BitmapPalette Palette
{
get
{
using (var fac = new ImagingFactory())
{
var palette = new Palette(fac);
try
{
Frame.CopyPalette(palette);
}
catch
{
// no indexed palette (PNG, JPG, etc.)
// it's a pity SharpDX throws here,
// it would be better to return null more gracefully as this is not really an error
// if you only want to support non indexed palette images, just return null for the property w/o trying to get a palette
return null;
}
var list = new List<Color>();
foreach (var c in palette.GetColors<int>())
{
var bytes = BitConverter.GetBytes(c);
var color = Color.FromArgb(bytes[3], bytes[2], bytes[1], bytes[0]);
list.Add(color);
}
return new BitmapPalette(list);
}
}
}
public override void CopyPixels(Int32Rect sourceRect, Array pixels, int stride, int offset)
{
if (offset != 0)
throw new NotSupportedException();
Frame.CopyPixels(
new SharpDX.Mathematics.Interop.RawRectangle(sourceRect.X, sourceRect.Y, sourceRect.Width, sourceRect.Height),
(byte[])pixels, stride);
}
public void Dispose() => Frame.Dispose();
public override event EventHandler<ExceptionEventArgs> DecodeFailed;
public override event EventHandler DownloadCompleted;
public override event EventHandler<ExceptionEventArgs> DownloadFailed;
public override event EventHandler<DownloadProgressEventArgs> DownloadProgress;
protected override Freezable CreateInstanceCore() => throw new NotImplementedException();
}
Related
I am attempting to create a Custom Drawable, but it is causing a Java.Lang.RuntimeException and Error inflating class android.widget.GridLayout when I try to use it. Here is the code:
using Android.App;
using Android.Graphics;
using Android.Graphics.Drawables;
namespace PointSaladScorekeepingAssistant
{
public class TextDrawable : Drawable
{
private Paint TextPaint;
public TextDrawable()
{
this.TextPaint = new Paint() { Color = Application.Context.Resources.GetColor(Resource.Color.Black, null), TextAlign = Paint.Align.Left, TextSize = 48 };
this.TextPaint.SetTypeface(Application.Context.Resources.GetFont(Resource.Font.HorsGarter));
}
public override int Opacity { get { return 255; } }
public override void Draw(Canvas canvas)
{
canvas.DrawColor(Application.Context.Resources.GetColor(Resource.Color.Orange, null));
Rect bounds = new Rect();
this.TextPaint.GetTextBounds("CABBAGE", 0, 7, bounds);
canvas.DrawText("CABBAGE", 0, 0, this.TextPaint);
}
public override void SetAlpha(int alpha) { this.TextPaint.Alpha = alpha; }
public override void SetColorFilter(ColorFilter colorFilter) { this.TextPaint.SetColorFilter(colorFilter); }
}
}
What is the problem?
I want do draw a custom string on the bottom right of an button with an PlatformEffect. Prefer to use an effect to be more flexible and apply this only to specific buttons and not application wide. The buttons are created dynamically without xaml.
Is this possible or do i need to create a custom button + renderer?
Thanks.
You could create your Custom button and then set the text alignment via custom renderer.
[assembly: ExportRenderer(typeof(Xamarin.Forms.Button), typeof(ButtonCustomRenderer))]//set the Button as your custom button
namespace App3.Droid
{
public class ButtonCustomRenderer : ButtonRenderer
{
public ButtonCustomRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
{
base.OnElementChanged(e);
Control.Gravity = GravityFlags.End // Set the horizontal text alignment to right
| GravityFlags.Bottom; // Set the vertical text alignment to bottom
}
}
}
I solved it by creating a renderer which overrides the Draw method.
public class HotkeyButtonRenderer : ButtonRenderer
{
HotkeyButton element;
public HotkeyButtonRenderer(Context ctx) : base(ctx)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
element = Element as HotkeyButton;
SetWillNotDraw(false);
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (element != null && e.PropertyName == HotkeyButton.HotkeyTextProperty.PropertyName)
{
Invalidate();
}
}
public override void Draw(Canvas canvas)
{
base.Draw(canvas);
if (element != null && !string.IsNullOrEmpty(element.HotkeyText))
{
string textToDraw = string.Format("({0})", element.HotkeyText);
float textSize = Control.TextSize / 1.5f;
SizeF measuredTextSize = GetTextSize(textToDraw, element.FontFamily, textSize);
float x = 5;
float y = Height-measuredTextSize.Height/2;
canvas.DrawText(textToDraw,
x,
y,
new TextPaint
{
Color = element.BorderColor.ToAndroid(),
TextSize = textSize
});
}
}
private SizeF GetTextSize(string text, string fontFamily, float textSize)
{
var textPaint = new SKPaint(new SKFont(SKTypeface.FromFamilyName(fontFamily), textSize));
SKRect textBounds = new SKRect();
textPaint.MeasureText(text, ref textBounds);
return new SizeF(textBounds.Width, textBounds.Height);
}
}
public class HotkeyButton : Button
{
public static readonly BindableProperty HotkeyTextProperty = BindableProperty.Create(
propertyName: nameof(HotkeyText),
returnType: typeof(string),
declaringType: typeof(MobileEntry),
defaultValue: string.Empty);
public string HotkeyText
{
get { return (string)GetValue(HotkeyTextProperty); }
set { SetValue(HotkeyTextProperty, value); }
}
}
I have been trying to set a bindable property value in my Element from my native control through a custom renderer. My native control is a view (painview) where you can draw and I am trying to get the drawing and set it, as a base64 string, to a bindable property Signature in my Element.
This is my Native Control
public class PaintView : View
{
Canvas _drawCanvas;
Bitmap _canvasBitmap;
readonly Paint _paint;
readonly Dictionary<int, MotionEvent.PointerCoords> _coords = new Dictionary<int, MotionEvent.PointerCoords>();
public Bitmap CanvasBitmap { get => _canvasBitmap; private set => _canvasBitmap = value; }
private readonly string TAG = nameof(PaintView);
public event EventHandler OnLineDrawn;
public PaintView(Context context) : base(context, null, 0)
{
_paint = new Paint() { Color = Color.Blue, StrokeWidth = 5f, AntiAlias = true };
_paint.SetStyle(Paint.Style.Stroke);
}
public PaintView(Context context, IAttributeSet attrs) : base(context, attrs) { }
public PaintView(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle) { }
protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
{
base.OnSizeChanged(w, h, oldw, oldh);
_canvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888); // full-screen bitmap
_drawCanvas = new Canvas(_canvasBitmap); // the canvas will draw into the bitmap
}
public override bool OnTouchEvent(MotionEvent e)
{
switch (e.ActionMasked)
{
case MotionEventActions.Down:
{
int id = e.GetPointerId(0);
var start = new MotionEvent.PointerCoords();
e.GetPointerCoords(id, start);
_coords.Add(id, start);
return true;
}
case MotionEventActions.PointerDown:
{
int id = e.GetPointerId(e.ActionIndex);
var start = new MotionEvent.PointerCoords();
e.GetPointerCoords(id, start);
_coords.Add(id, start);
return true;
}
case MotionEventActions.Move:
{
for (int index = 0; index < e.PointerCount; index++)
{
var id = e.GetPointerId(index);
float x = e.GetX(index);
float y = e.GetY(index);
_drawCanvas.DrawLine(_coords[id].X, _coords[id].Y, x, y, _paint);
_coords[id].X = x;
_coords[id].Y = y;
OnLineDrawn?.Invoke(this, EventArgs.Empty);
}
Invalidate();
return true;
}
case MotionEventActions.PointerUp:
{
int id = e.GetPointerId(e.ActionIndex);
_coords.Remove(id);
return true;
}
case MotionEventActions.Up:
{
int id = e.GetPointerId(0);
_coords.Remove(id);
return true;
}
default:
return false;
}
}
protected override void OnDraw(Canvas canvas)
{
// Copy the off-screen canvas data onto the View from it's associated Bitmap (which stores the actual drawn data)
canvas.DrawBitmap(_canvasBitmap, 0, 0, null);
}
public void Clear()
{
_drawCanvas.DrawColor(Color.Black, PorterDuff.Mode.Clear); // Paint the off-screen buffer black
Invalidate(); // Call Invalidate to redraw the view
}
public void SetInkColor(Color color)
{
_paint.Color = color;
}
}
The property PaintView._canvasBitmap is the one I want to be set in my Xamarin.Form Element through my custom renderer.
This is my Custom Renderer
public class SketchViewRenderer : ViewRenderer<SketchView, PaintView>
{
public SketchViewRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<SketchView> e)
{
if (Control == null)
{
var paintView = new PaintView(Context);
paintView.SetInkColor(Element.InkColor.ToAndroid());
SetNativeControl(new PaintView(Context));
MessagingCenter.Subscribe<SketchView>(this, nameof(SketchView.OnClear), OnMessageClear);
Control.OnLineDrawn += PaintViewLineDrawn;
}
}
private void PaintViewLineDrawn(object sender, EventArgs e)
{
var sketchCrl = (ISketchViewController)Element;
if (sketchCrl == null) return;
try
{
Element.SetValueFromRenderer(SketchView.SignatureProperty, Utils.Utils.BitmapToBase64(Control.CanvasBitmap));
sketchCrl.SendSketchUpdated(Utils.Utils.BitmapToBase64(Control.CanvasBitmap));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == SketchView.InkColorProperty.PropertyName)
{
Control.SetInkColor(Element.InkColor.ToAndroid());
}
if (e.PropertyName == SketchView.ClearProperty.PropertyName)
{
if (Element.Clear) OnMessageClear(Element);
}
}
private void OnMessageClear(SketchView sender)
{
if (sender == Element) Control.Clear();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
MessagingCenter.Unsubscribe<SketchView>(this, nameof(SketchView.OnClear));
Control.OnLineDrawn -= PaintViewLineDrawn;
}
base.Dispose(disposing);
}
}
I have tried changing my Element.Signature property through the SketchViewRenderer.PaintViewLineDrawn(...) method without success. This has been prove when debugging my view model where the property has not been set as expected.
My Xamarin.Forms Element looks as follow
public class SketchView : View, IDoubleTappedController, ISketchViewController
{
public static readonly BindableProperty SignatureProperty = BindableProperty.Create(nameof(Signature), typeof(string), typeof(SketchView), null, defaultBindingMode: BindingMode.TwoWay);
public string Signature
{
get => (string)GetValue(SignatureProperty);
set => SetValue(SignatureProperty, value);
}
public static readonly BindableProperty MultiTouchEnabledProperty = BindableProperty.Create(nameof(MultiTouchEnabled), typeof(bool), typeof(SketchView), false);
public bool MultiTouchEnabled
{
get => (bool)GetValue(MultiTouchEnabledProperty);
set => SetValue(MultiTouchEnabledProperty, value);
}
public static readonly BindableProperty InkColorProperty = BindableProperty.Create(nameof(InkColor), typeof(Xamarin.Forms.Color), typeof(SketchView), Xamarin.Forms.Color.Azure);
public Xamarin.Forms.Color InkColor
{
get => (Xamarin.Forms.Color)GetValue(InkColorProperty);
set => SetValue(InkColorProperty, value);
}
public static readonly BindableProperty ClearProperty = BindableProperty.Create(nameof(Clear), typeof(bool), typeof(SketchView), false, defaultBindingMode: BindingMode.TwoWay);
public bool Clear
{
get => (bool)GetValue(ClearProperty);
set
{
SetValue(ClearProperty, value);
if (value) { OnClear(); }
}
}
public void OnClear()
{
MessagingCenter.Send(this, nameof(OnClear));
}
public void SetSignature(string signature)
{
Signature = signature;
}
void IDoubleTappedController.DoubleTapped()
{
throw new NotImplementedException();
}
void ISketchViewController.SendSketchUpdated(string signature)
{
Clear = false;
Signature = signature;
}
}
I have also tried using the SetValueFromRenderer() method from my Custom renderer, again, without success.
May you suggest to me what is the way to set an Element value from a Custom Renderer?
Thanks and kind regards,
Temo
The problem was that the field in my view model was set to null when comparing it with the value. Then throwing a TargetException letting the source buggy unable to be updated by the target.
public bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = default)
{
if (value == null) return false;
if (field != null && field.Equals(value)) return false;
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
Now, I make sure the field is not null before using the Equals operator.
I have a xamarin forms image, and when the user taps on the image I want to get the x,y coordinates of where the user tapped. Not the x,y coordinates of the view per se, but the coordinates within the image. I have this working on Android below. What would the iOS custom renderer be like?
Create a interface for touch event:
public interface IFloorplanImageController
{
void SendTouched();
}
Create a custom control for image:
public class FloorplanImage : Image, IFloorplanImageController
{
public event EventHandler Touched;
public void SendTouched()
{
Touched?.Invoke(this, EventArgs.Empty);
}
public Tuple<float, float> TouchedCoordinate
{
get { return (Tuple<float, float>)GetValue(TouchedCoordinateProperty); }
set { SetValue(TouchedCoordinateProperty, value); }
}
public static readonly BindableProperty TouchedCoordinateProperty =
BindableProperty.Create(
propertyName: "TouchedCoordinate",
returnType: typeof(Tuple<float, float>),
declaringType: typeof(FloorplanImage),
defaultValue: new Tuple<float, float>(0, 0),
propertyChanged: OnPropertyChanged);
public static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
}
}
Implement the custom renderer:
[assembly: ExportRenderer(typeof(FloorplanImage), typeof(FloorplanImageRenderer))]
namespace EmployeeApp.Droid.Platform
{
public class FloorplanImageRenderer : ImageRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
if (Control != null)
{
Control.Clickable = true;
Control.SetOnTouchListener(ImageTouchListener.Instance.Value);
Control.SetTag(Control.Id, new JavaObjectWrapper<FloorplanImage> { Obj = Element as FloorplanImage });
}
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (Control != null)
{
Control.SetOnTouchListener(null);
}
}
base.Dispose(disposing);
}
private class ImageTouchListener : Java.Lang.Object, Android.Views.View.IOnTouchListener
{
public static readonly Lazy<ImageTouchListener> Instance = new Lazy<ImageTouchListener>(
() => new ImageTouchListener());
public bool OnTouch(Android.Views.View v, MotionEvent e)
{
var obj = v.GetTag(v.Id) as JavaObjectWrapper<FloorplanImage>;
var element = obj.Obj;
var controller = element as IFloorplanImageController;
if (e.Action == Android.Views.MotionEventActions.Down)
{
var x = e.GetX();
var y = e.GetY();
element.TouchedCoordinate = new Tuple<float, float>(x, y);
controller?.SendTouched();
}
else if (e.Action == Android.Views.MotionEventActions.Up)
{
}
return false;
}
}
}
public class JavaObjectWrapper<T> : Java.Lang.Object
{
public T Obj { get; set; }
}
}
Use this control like this:
<local:FloorplanImage HeightRequest="300" x:Name="image" WidthRequest="300"
Aspect="AspectFit" Touched="image_Touched" />
code behind:
private void image_Touched(object sender, EventArgs e)
{
var cor = image.TouchedCoordinate;
}
I found this Customer success example on github that does exactly this.
[assembly: ExportRenderer(typeof(CustomImage), typeof(CustomImageRenderer))]
namespace FormsImageTapGesture.iOS
{
public class CustomImageRenderer : ImageRenderer
{
#region properties & fields
// ---------------------------------------------------------------------------
//
// PROPERTIES & FIELDS
//
// ---------------------------------------------------------------------------
private UIImageView nativeElement;
private CustomImage formsElement;
#endregion
#region methods
// ---------------------------------------------------------------------------
//
// METHODS
//
// ---------------------------------------------------------------------------
//
// Set up the custom renderer. In this case, that means set up the gesture
// recognizer.
//
protected override void OnElementChanged(ElementChangedEventArgs<Image> e) {
base.OnElementChanged (e);
if (e.NewElement != null) {
// Grab the Xamarin.Forms control (not native)
formsElement = e.NewElement as CustomImage;
// Grab the native representation of the Xamarin.Forms control
nativeElement = Control as UIImageView;
// Set up a tap gesture recognizer on the native control
nativeElement.UserInteractionEnabled = true;
UITapGestureRecognizer tgr = new UITapGestureRecognizer (TapHandler);
nativeElement.AddGestureRecognizer (tgr);
}
}
//
// Respond to taps.
//
public void TapHandler(UITapGestureRecognizer tgr) {
CGPoint touchPoint = tgr.LocationInView (nativeElement);
formsElement.OnTapEvent ((int)touchPoint.X, (int)touchPoint.Y);
}
#endregion
}
}
I've got a simple test app with a basic custom FrameworkElement implementation (TestElement below). The TestElement creates a couple of drawing visuals and draws some stuff in the constructor to a width of 600. It also implements the necessary bits of IScrollinfo; The Window containing the element has got a scrollviewer and a max size of 300x300. The scrollbar appears but does not scroll the content of the TestElement.
Can anyone suggest whether what I am trying to do is possible and if so what I am doing wrong. I could re-render the drawing visuals in SetHorizontalOffset but don't want to for performance reasons as I have already drawn all I need to.
I hope the question makes some sense - let me know if not and I can clarify.
Many thanks - Karl
public class TestElement : FrameworkElement, IScrollInfo
{
DrawingVisual visual;
DrawingVisual visual2;
public TestElement()
{
Draw();
this.MaxWidth = 600;
this.MaxHeight = 300;
}
public void Draw()
{
if(visual == null)
{
visual = new DrawingVisual();
base.AddVisualChild(visual);
base.AddLogicalChild(visual);
}
if (visual2 == null)
{
visual2 = new DrawingVisual();
base.AddVisualChild(visual2);
base.AddLogicalChild(visual2);
}
Random rand = new Random();
var pen = new Pen(Brushes.Black, 1);
using(var dc = visual.RenderOpen())
{
for (int i = 0; i < 400; i++)
{
var r = rand.Next(10, 200);
dc.DrawLine(pen, new Point(i, r), new Point(i, 0));
}
}
using (var dc = visual2.RenderOpen())
{
for (int i = 0; i < 200; i++)
{
var r = rand.Next(10, 200);
dc.DrawLine(pen, new Point(i, r), new Point(i, 0));
}
visual2.Offset = new Vector(400, 0);
}
}
protected override int VisualChildrenCount
{
get { return 2; }
}
protected override Visual GetVisualChild(int index)
{
return index == 0 ? visual : visual2;
}
protected override Size MeasureOverride(Size availableSize)
{
viewport = availableSize;
owner.InvalidateScrollInfo();
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
var value = base.ArrangeOverride(finalSize);
return base.ArrangeOverride(finalSize);
}
Point offset = new Point(0,0);
public void SetHorizontalOffset(double offset)
{
this.offset.X = offset;
this.InvalidateArrange();
}
public void SetVerticalOffset(double offset)
{
this.offset.Y = offset;
}
public Rect MakeVisible(Visual visual, Rect rectangle)
{
throw new NotImplementedException();
}
public bool CanVerticallyScroll { get; set; }
public bool CanHorizontallyScroll { get; set; }
Size extent = new Size(600, 300);
private Size viewport = new Size(0, 0);
public double ExtentWidth
{
get { return extent.Width; }
}
public double ExtentHeight
{
get {return extent.Height; }
}
public double ViewportWidth
{
get { return viewport.Width; }
}
public double ViewportHeight
{
get { return viewport.Height; }
}
public double HorizontalOffset
{
get { return offset.X; }
}
public double VerticalOffset
{
get { return offset.Y; }
}
private ScrollViewer owner;
public ScrollViewer ScrollOwner
{
get { return owner; }
set { owner = value; }
}
}
the xaml:
<Window x:Class="TestWpfApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l ="clr-namespace:TestWpfApp"
Title="TestWpfApp" Height="300" Width="300" >
<Grid>
<ScrollViewer CanContentScroll="True" HorizontalScrollBarVisibility="Visible">
<l:TestElement CanHorizontallyScroll="True" />
</ScrollViewer>
</Grid>
Just had a beer too and it helps to find a solution. ;-)
It's not the ultimative "all well done" solution, but for sure it will help you further:
In your implementation of ArrangeOverride, try:
protected override Size ArrangeOverride(Size finalSize)
{
this.visual.Offset = new Vector(-this.HorizontalOffset, -this.VerticalOffset);
var value = base.ArrangeOverride(finalSize);
return base.ArrangeOverride(finalSize);
}
Basically you have to move your objects yourself.
For more information see this article too: iscrollinfo tutorial
.
Normally you would have to use Transformations to move the objects there where you scrolled them.