I'm developing Pong with MvvmCross. The Paddles Y values change when holding the up and down buttons (buttons in the android view/activity - not keyboard buttons). However this is not being shown in the view (paddles stay in one spot, even though I can see in the console logs that the paddle has gone up or down).
Why are the paddles Y values not properly bound to the view?
Here is the code:
ViewModel:
using System.Linq;
using System.Text;
using System.Threading;
using Cirrious.MvvmCross.ViewModels;
using Pong.Core.ViewModels;
using Pong.Core.Models;
namespace Pong.Core.ViewModels
{
public class GamePlayViewModel
: MvxViewModel
{
private string _hello = "Hello MvvmCross";
public string Hello
{
get { return _hello; }
set { _hello = value; RaisePropertyChanged(() => Hello); }
}
private int _totalFramesBeenHad;
public int TotalFramesBeenHad
{
get { return _totalFramesBeenHad; }
set { _totalFramesBeenHad = value; RaisePropertyChanged(() => TotalFramesBeenHad); }
}
private PlayerPaddle _paddle1;
public int Paddle1
{
get { return _paddle1.Y; }
set { _paddle1.Y = value; RaisePropertyChanged(() => Paddle1); }
}
private ComputerPaddle _paddle2;
public int Paddle2
{
get { return _paddle2.Y; }
set { _paddle2.Y = value; RaisePropertyChanged(() => Paddle2); }
}
protected StandardBall StandardBall;
public GamePlayViewModel()
{
_paddle1 = new PlayerPaddle();
_paddle2 = new ComputerPaddle();
StandardBall = new StandardBall();
}
public void UpdatePaddle1()
{
switch (_paddle1.DetectWallCollision())
{
case "upper":
_paddle1.UpperWallHit();
break;
case "lower":
_paddle1.LowerWallHit();
break;
case "none":
_paddle1.MoveOneFrame();
break;
}
}
public void UpdateBall()
{
if (StandardBall.DetectWallCollision()) StandardBall.HandleWallCollision();
StandardBall.MoveOneFrame();
}
public void SetPaddleDirection(string direction)
{
_paddle1.SetDirection(direction);
}
public void StopPaddle()
{
_paddle1.StopMoving();
}
}
}
child viewmodel (actual one used):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Pong.Core.ViewModels;
using Pong.Core.Models;
using Pong.Droid.Views;
namespace Pong.Droid.ViewModels
{
public class GamePlayViewModelAndroid : GamePlayViewModel
{
public readonly Timer _dispatcherTimer;
public GamePlayView gpv;
public GamePlayViewModelAndroid (GamePlayView gpv)
{
this.gpv = gpv;
TimerCallback timerDelegate = new TimerCallback (Tick);
_dispatcherTimer = new Timer (timerDelegate, null, 0, 1000/Court.FPS);
}
public void Tick(object state)
{
UpdatePaddle1();
gpv.move ();
}
}
}
View:
using Android.App;
using Android.OS;
using Cirrious.MvvmCross.Droid.Views;
using Cirrious;
using Cirrious.CrossCore;
using Cirrious.MvvmCross.Binding;
using Cirrious.MvvmCross.ViewModels;
using Pong.Droid.ViewModels;
using Android.Content.PM;
using Pong.Droid;
using Android.Views;
using Android.Widget;
using Android.Graphics;
using Android.Content;
using Android.Content.Res;
using Cirrious.MvvmCross.Binding.BindingContext;
using Pong.Core.Models;
namespace Pong.Droid.Views
{
[Activity(Label = "!PONG!", ScreenOrientation = ScreenOrientation.Landscape)]
public class GamePlayView : MvxActivity
{
private GamePlayViewModelAndroid _viewModel;
private Button _buttonUp;
private Button _buttonDown;
public GameView GameView;
public LinearLayout ParentLayout;
public LinearLayout ButtonsLayout;
public int _paddle1y;
public int _paddle2y;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
GameView = new GameView (this);
_viewModel = new GamePlayViewModelAndroid(this);
SetUpControls ();
SetUpButtonEvents ();
SetContentView(ParentLayout);
DataContext = _viewModel;
this.ClearAllBindings();
var set = this.CreateBindingSet<GamePlayView, GamePlayViewModelAndroid>();
set.Bind(this).For(v => v._paddle1y).To(vm => vm.Paddle1);
set.Bind(this).For(v => v._paddle2y).To(vm => vm.Paddle2);
set.Apply();
}
void SetUpButtonEvents ()
{
_buttonUp.Touch += (s, e) => {
var handled = false;
if (e.Event.Action == MotionEventActions.Down) {
_viewModel.SetPaddleDirection ("up");
handled = true;
}
else
if (e.Event.Action == MotionEventActions.Up) {
_viewModel.StopPaddle ();
handled = true;
}
e.Handled = handled;
};
_buttonDown.Touch += (s, e) => {
var handled = false;
if (e.Event.Action == MotionEventActions.Down) {
_viewModel.SetPaddleDirection ("down");
handled = true;
}
else
if (e.Event.Action == MotionEventActions.Up) {
_viewModel.StopPaddle ();
handled = true;
}
e.Handled = handled;
};
}
void SetUpControls ()
{
_buttonUp = new Button (this);
_buttonDown = new Button (this);
ParentLayout = new LinearLayout (this);
ButtonsLayout = new LinearLayout (this);
ParentLayout.Orientation = Android.Widget.Orientation.Horizontal;
ButtonsLayout.Orientation = Android.Widget.Orientation.Vertical;
ButtonsLayout.AddView (_buttonUp);
ButtonsLayout.AddView (_buttonDown);
ParentLayout.AddView (ButtonsLayout);
ParentLayout.AddView (GameView);
}
public void move() {
//GameView.paddle1y = _viewModel.Paddle1.Y;
//GameView.paddle2y = _viewModel.Paddle2.Y;
RunOnUiThread (() => GameView.Invalidate ());
}
}
public class GameView : View {
private Bitmap _paddleBmp;
private int _paddle1x;
public int _paddle1y;
private int _paddle2x;
public int _paddle2y;
public GamePlayViewModelAndroid vm;
public GamePlayView View;
public GameView(Context context) : base (context) {
SetPaddleBmp ();
// this.ClearAllBindings();
// var set = this.CreateBindingSet<GameView, GamePlayViewModelAndroid>();
// set.Bind(_paddle1y).To(vm => vm.Paddle1.Y);
// set.Bind(_paddle2y).To(vm => vm.Paddle2.Y);
// set.Apply();
//var set = this.CreateBindingSet<PolicySummaryCell, PolicyComponent<BasePolicy>>();
//set.Bind(_periodOfInsurance).To(vm => vm.PeriodOfInsurance);
//set.Bind(_title).To(vm => vm.Title);
View = (GamePlayView)context;
}
void SetPaddleBmp ()
{
var paddlebmpTemp = BitmapFactory.DecodeResource (Resources, Resource.Drawable.Icon);
_paddleBmp = Bitmap.CreateScaledBitmap (paddlebmpTemp, Paddle.Width, Paddle.Height, false);
}
protected override void OnDraw(Canvas canvas) {
canvas.DrawColor(Color.Aqua);
canvas.DrawBitmap (_paddleBmp, _paddle1x, View._paddle1y, null);
canvas.DrawBitmap (_paddleBmp, _paddle2x, View._paddle2y, null);
}
protected override void OnSizeChanged(int w, int h, int oldw, int oldh) {
SetUpCourt (w, h);
}
void SetUpCourt (int w, int h)
{
Court.Width = w;
Court.Height = h;
Court.UpperBound = 0;
Court.LowerBound = h;
Court.LeftBound = 0;
Court.RightBound = w;
ComputerPaddle.X = Court.RightBound - Paddle.Width - 20;
_paddle2x = ComputerPaddle.X;
_paddle1x = PlayerPaddle.X;
}
}
}
Model:
using System.Diagnostics;
namespace Pong.Core.Models
{
public class Paddle
{
public int Y { get; set; }
public int VY { get; set; }
public static readonly int Speed = 600;
public static readonly int Height = 300;
public static readonly int Width = 100;
public void StopMoving()
{
VY = 0;
}
public void SetDirection(string direction)
{
if (direction == "up")
{
VY = -Speed;
}
else if (direction == "down")
{
VY = Speed;
}
}
public string DetectWallCollision()
{
if (Y < Court.UpperBound)
{
return "upper";
}
if (Y > (Court.LowerBound - Paddle.Height))
{
return "lower";
}
return "none";
}
public void UpperWallHit()
{
StopMoving();
Y = Court.UpperBound;
Debug.WriteLine("You hit the top wall");
}
public void LowerWallHit()
{
StopMoving();
Y = Court.LowerBound - Paddle.Height;
Debug.WriteLine("You hit the bottom wall");
}
public void MoveOneFrame()
{
Y += VY/Court.FPS;//this should trigger the RaisePropertyChanged(() => Paddle1)
}
}
public class PlayerPaddle : Paddle {
public static readonly int X = 20;
}
public class ComputerPaddle : Paddle {
public static int X;
}
}
I think your problem here, is that you're updating the Paddle.Y field, but the RaisePropertyChanged() called as your update the Paddle (on set).
See the difference?
Only if you'll set new instance of Paddle1 and Paddle2 property in GamePlayViewModel the setter will be called:
Paddle1 = new Paddle(); //will call the setter of Paddle1 property
Paddle1.Y = 90; //would not call the setter property
What you need to do is to call RaisePropertyChanged when you update the X and Y values of Paddle1 and Paddle2 properties.
The answer to this question is that on the view, to bind a field to the viewModel programmatically, the field must be a property with a getter and setter. Just like this. In the view:
public int _paddle1y { get; set; }
public int _paddle2y { get; set; }
I don't know why this is the case. I think it's a bit of a bug in MvvmCross. But maybe there is a valid reason for it.
Instead of calling _paddle1 directly, call Paddle1 (and 2) so the RaisePropertyChanged event will get called.
Related
I'm trying to implement a theme changer to my app. So far I've done this:
BaseForm - has all the functions to be inherited from, it's a lengthy one so if required I'll post it here. What troubles I'm having is as follows:
MainForm has a button to open UserSettings where the user can change the theme. Problem is, I managed to get it down that the theme applies, but only for the UserSettings window, and I want it to apply to anything that is open (if there is anything else).
Edit: adding more code
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Text;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Windows.Forms;
using IniParser;
using ME___Tooling_Designer_App.Properties;
namespace ME___Tooling_Designer_App.Configuration;
public partial class UserSettings : BaseForm
{
public UserSettings()
{
InitializeComponent();
ApplyTheme();
ReadConfiguration();
}
// this is where the theme should be applied upon close
private void CBoxTheme_DropDownClosed(object sender, EventArgs e)
{
var parser = new FileIniDataParser();
var data = parser.ReadFile(_filePathSettings);
if (cBoxTheme.SelectedIndex == 0)
{
data["User_Settings"]["Theme"] = "Dark";
parser.WriteFile(_filePathSettings, data);
GlobalTheme = Enumerators.Theme.Dark;
}
if (cBoxTheme.SelectedIndex == 1)
{
data["User_Settings"]["Theme"] = "Light";
parser.WriteFile(_filePathSettings, data);
GlobalTheme = Enumerators.Theme.Light;
}
ApplyTheme();
}
public override void ApplyThemeSpecific(ThemeSettings settings)
{
panel1.BackColor = settings.PrimaryColor;
label2.ForeColor = settings.PrimaryFontColor;
btnExit.BackColor = settings.PrimaryColor;
btnExit.ForeColor = settings.PrimaryFontColor;
groupBox1.ForeColor = settings.PrimaryFontColor;
groupBox2.ForeColor = settings.PrimaryFontColor;
panel2.BackColor = settings.SecondaryColor;
}
Edit: this is BaseForm code:
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Text;
using System.Windows.Forms;
namespace ME___Tooling_Designer_App.Configuration;
public class BaseForm : Form
{
public static List<PrivateFontCollection> FontCollections;
public static Enumerators.Theme GlobalTheme { get; set; }
public void ApplyTheme()
{
var settings = new ThemeSettings();
if (GlobalTheme == Enumerators.Theme.Dark)
{
settings.PrimaryColor = Color.FromArgb(32, 30, 50);
settings.SecondaryColor = Color.FromArgb(32, 30, 45);
settings.TertiaryColor = Color.FromArgb(11, 7, 17);
settings.QuaternaryColor = Color.FromArgb(23, 21, 32);
settings.QuinaryColor = Color.FromArgb(35, 32, 39);
settings.SenaryColor = Color.LightGray;
settings.SeptenaryColor = Color.Gainsboro;
settings.PrimaryFontColor = Color.Gainsboro;
}
else if (GlobalTheme == Enumerators.Theme.Light)
{
settings.PrimaryColor = Color.LightGray;
settings.SecondaryColor = Color.GhostWhite;
settings.TertiaryColor = Color.Gainsboro;
settings.QuaternaryColor = Color.DarkGray;
settings.QuinaryColor = Color.Silver;
settings.SenaryColor = Color.Gray;
settings.SeptenaryColor = Color.Black;
settings.PrimaryFontColor = Color.Black;
}
ApplyThemeSpecific(settings);
}
public virtual void ApplyThemeSpecific(ThemeSettings settings)
{
}
}
public class Enumerators
{
public enum Theme
{
NotSet,
Light,
Dark
}
}
public class ThemeSettings
{
public Color PrimaryColor { get; set; }
public Color SecondaryColor { get; set; }
public Color TertiaryColor { get; set; }
public Color QuaternaryColor { get; set; }
public Color QuinaryColor { get; set; }
public Color SenaryColor { get; set; }
public Color SeptenaryColor { get; set; }
public Color PrimaryFontColor { get; set; }
}
this is my Program.cs:
using System;
using System.Windows.Forms;
using IniParser;
using ME___Tooling_Designer_App.Configuration;
using ME___Tooling_Designer_App.Forms;
namespace ME___Tooling_Designer_App
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
var parser = new FileIniDataParser();
var data = parser.ReadFile(Application.StartupPath + #"\" + "Config" + #"\" + "settings.ini");
var themeSetting = data["User_Settings"]["Theme"];
Application.EnableVisualStyles();
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.SetCompatibleTextRenderingDefault(false);
if (themeSetting == "Light")
{
BaseForm.GlobalTheme = Enumerators.Theme.Light;
}
else if (themeSetting == "Dark")
{
BaseForm.GlobalTheme = Enumerators.Theme.Dark;
}
else
{
BaseForm.GlobalTheme = Enumerators.Theme.NotSet;
}
Application.Run(new Login());
}
}
}
tried doing it as MainForm.ApplyTheme, but that just returns to UserSettings nonetheless
Edit: adding snippets of MainForm:
namespace ME___Tooling_Designer_App.Forms;
public partial class MainForm : BaseForm
{
/// <summary>
/// Constructor
/// </summary>
public MainForm()
{
InitializeComponent();
ApplyTheme();
CreateDirectories();
CustomizeDesign();
InitializeDatFiles();
InitializeIniConfiguration();
ReadFont();
ReadComputer();
ReadSystem();
TestTrial();
DaysRemainColoring();
CompareComputerNameAndLicense();
lblUser.Text = Environment.UserName;
}
public override void ApplyThemeSpecific(ThemeSettings settings)
{
panelTopBar.BackColor = settings.PrimaryColor;
btnInfo.BackColor = settings.PrimaryColor;
btnMinimize.BackColor = settings.PrimaryColor;
btnExitTop.BackColor = settings.PrimaryColor;
panelCenter.BackColor = settings.SecondaryColor;
panelSideMenu.BackColor = settings.TertiaryColor;
panelSideMenuLogo.BackColor = settings.TertiaryColor;
btnMainMenu.BackColor = settings.TertiaryColor;
btnSettings.BackColor = settings.TertiaryColor;
btnSave.BackColor = settings.TertiaryColor;
btnLoad.BackColor = settings.TertiaryColor;
btnExit.BackColor = settings.TertiaryColor;
panelQuickView.BackColor = settings.QuaternaryColor;
panelMainMenu.BackColor = settings.QuinaryColor;
panelSettings.BackColor = settings.QuinaryColor;
btnNewProject.BackColor = settings.QuinaryColor;
btnExistingProject.BackColor = settings.QuinaryColor;
btnReadProject.BackColor = settings.QuinaryColor;
btnSearchProject.BackColor = settings.QuinaryColor;
btnUserSettings.BackColor = settings.QuinaryColor;
btnAppSettings.BackColor = settings.QuinaryColor;
}
The solution is to add a timer
Because your problem is to update all windows, you just need to add a timer control to each window.
Timer:
BaseForm:
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApp4 {
public partial class BaseForm : Form {
public BaseForm () {
}
private static Image baseImage=null;
public static Image BaseImage {
get {
return baseImage;
}
set {
baseImage = value;
}
}
}
}
SettingsForm:
using System;
namespace WindowsFormsApp4 {
public partial class SettingsForm : BaseForm {
public SettingsForm () {
InitializeComponent();
}
private void button1_Click (object sender, EventArgs e) {
BaseImage = Resource.tiger;
}
private void button2_Click (object sender, EventArgs e) {
BaseImage = Resource.windows;
}
private void timer1_Tick (object sender, EventArgs e) {
this.BackgroundImage = BaseImage;
}
}
}
Form1:
using System;
namespace WindowsFormsApp4 {
public partial class Form1 : BaseForm {
public Form1 () {
InitializeComponent();
}
private void button1_Click (object sender, EventArgs e) {
SettingsForm settingsForm=new SettingsForm();
settingsForm.ShowDialog();
}
private void timer1_Tick_1 (object sender, EventArgs e) {
this.BackgroundImage = BaseImage;
}
}
}
Resource.resx:
OutPut:
You can change it according to your needs.
If you have any questions, please comment below, and I will continue to follow up.
Try this code on all forms of your project on Activated and Load events.
Such as, MainForm:
private void MainForm_Activated(object sender, EventArgs e)
{
ApplyTheme();
}
Update: Try following line on BaseForm, if it works, add override on all forms and change the color values of all elements:
public virtual void ApplyThemeSpecific(ThemeSettings settings)
{
Background = settings.PrimaryColor; //for example
}
Update 2: Add a timer on all forms that checks theme change, and when theme changes, it changes the visual. Or you can make a event when theme changes.
I need to display radio options as a grid and found this gist offering a RadioGridGroup class I can use instead of the RadioGroup provided in Android. (https://gist.github.com/saiaspire/a73135cfee1110a64cb0ab3451b6ca33)
I have converted everything into the code below and just wanted to ask some questions.
(1) Is it okay that I just deleted all the "final" keywords? ie int result = sNextGeneratedId.Get();
(2) Is converting parent == RadioGridGroup.this && child instanceof AppCompatRadioButton to parent is RadioGridGroup && child is AppCompatRadioButton an accurate conversion?
(3) In the CheckedStateTracker which extends/implements a Listener.. It has access to the RadioGridGroup fields like mProtectFromCheckedChange in the Java example, but in the C# example, those variables are inaccessable there. How do I solve this?
(4) For the listeners I had them implement Java.Lang.Object as well so I don't need to implement the Dispose side CheckedStateTracker implements CompoundButton.OnCheckedChangeListener became CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
using System;
using Android.Content;
using Android.Support.V7;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Views.Accessibility;
using Android.Widget;
using Java.Util.Concurrent.Atomic;
/**
* https://stackoverflow.com/questions/60764344/how-to-convert-listener-from-java-to-c-sharp
*
* <p>This class is used to create a multiple-exclusion scope for a set of radio
* buttons. Checking one radio button that belongs to a radio group unchecks
* any previously checked radio button within the same group.</p>
* <p/>
* <p>Intially, all of the radio buttons are unchecked. While it is not possible
* to uncheck a particular radio button, the radio group can be cleared to
* remove the checked state.</p>
* <p/>
* <p>The selection is identified by the unique id of the radio button as defined
* in the XML layout file.</p>
* <p/>
* <p>See
* {#link android.widget.GridLayout.LayoutParams GridLayout.LayoutParams}
* for layout attributes.</p>
*
* #see AppCompatRadioButton
*/
public class RadioGridGroup: Android.Support.V7.Widget.GridLayout
{
private static AtomicInteger sNextGeneratedId = new AtomicInteger(1);
private int mCheckedId = -1;
private CompoundButton.IOnCheckedChangeListener mChildOnCheckedChangeListener;
private bool mProtectFromCheckedChange = false;
private OnCheckedChangeListener mOnCheckedChangeListener;
private PassThroughHierarchyChangeListener mPassThroughListener;
public RadioGridGroup(Context context) : base(context)
{
init();
}
public RadioGridGroup(Context context, IAttributeSet attrs): base(context, attrs)
{
init();
}
private void init()
{
mChildOnCheckedChangeListener = new CheckedStateTracker();
mPassThroughListener = new PassThroughHierarchyChangeListener();
base.SetOnHierarchyChangeListener(mPassThroughListener);
}
public override void SetOnHierarchyChangeListener(IOnHierarchyChangeListener listener)
{
mPassThroughListener.mOnHierarchyChangeListener = listener;
}
protected override void OnFinishInflate()
{
base.OnFinishInflate();
if (mCheckedId != -1)
{
mProtectFromCheckedChange = true;
SetCheckedStateForView(mCheckedId, true);
mProtectFromCheckedChange = false;
setCheckedId(mCheckedId);
}
}
public override void AddView(View child, int index, ViewGroup.LayoutParams prs)
{
if (child is AppCompatRadioButton) {
AppCompatRadioButton button = (AppCompatRadioButton)child;
if (button.Checked)
{
mProtectFromCheckedChange = true;
if (mCheckedId != -1)
{
SetCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
setCheckedId(button.Id);
}
}
base.AddView(child, index, prs);
}
public void check(int id)
{
if (id != -1 && (id == mCheckedId))
{
return;
}
if (mCheckedId != -1)
{
SetCheckedStateForView(mCheckedId, false);
}
if (id != -1)
{
SetCheckedStateForView(id, true);
}
setCheckedId(id);
}
private void setCheckedId(int id)
{
mCheckedId = id;
if (mOnCheckedChangeListener != null)
{
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
}
}
private void SetCheckedStateForView(int viewId, bool chkd)
{
View checkedView = FindViewById(viewId);
if (checkedView != null && checkedView is AppCompatRadioButton) {
((AppCompatRadioButton)checkedView).Checked = (chkd);
}
}
public int GetCheckedCheckableImageButtonId()
{
return mCheckedId;
}
public void clearCheck()
{
check(-1);
}
public void SetOnCheckedChangeListener(OnCheckedChangeListener listener)
{
mOnCheckedChangeListener = listener;
}
public override void OnInitializeAccessibilityEvent(AccessibilityEvent ev)
{
base.OnInitializeAccessibilityEvent(ev);
ev.ClassName = (this.GetType().Name);
}
public override void OnInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
{
base.OnInitializeAccessibilityNodeInfo(info);
info.ClassName = (this.GetType().Name);
}
public interface OnCheckedChangeListener
{
void onCheckedChanged(RadioGridGroup group, int checkedId);
}
internal class CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
{
public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
{
if (mProtectFromCheckedChange)
{
return;
}
mProtectFromCheckedChange = true;
if (mCheckedId != -1)
{
SetCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
int id = buttonView.Id;
SetCheckId(id);
}
}
internal class PassThroughHierarchyChangeListener : Java.Lang.Object
ViewGroup.IOnHierarchyChangeListener
{
internal ViewGroup.IOnHierarchyChangeListener mOnHierarchyChangeListener;
public void OnChildViewAdded(View parent, View child)
{
if (parent is RadioGridGroup && child is AppCompatRadioButton) {
int id = child.Id;
// generates an id if it's missing
if (id == View.NoId)
{
id = View.GenerateViewId();
child.Id = (id);
}
((AppCompatRadioButton)child).SetOnCheckedChangeListener(
mChildOnCheckedChangeListener);
}
if (mOnHierarchyChangeListener != null)
{
mOnHierarchyChangeListener.OnChildViewAdded(parent, child);
}
}
public void OnChildViewRemoved(View parent, View child)
{
if (parent is RadioGridGroup && child is AppCompatRadioButton) {
((AppCompatRadioButton)child).SetOnCheckedChangeListener(null);
}
if (mOnHierarchyChangeListener != null)
{
mOnHierarchyChangeListener.OnChildViewRemoved(parent, child);
}
}
}
public static int GenerateViewId()
{
for (; ; )
{
int result = sNextGeneratedId.Get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.CompareAndSet(result, newValue))
{
return result;
}
}
}
}
UPDATE:
Below is my code which includes passing the enclosing parent to the listeners, and using those to compare the parent as well as to access the parent's fields. I've also added readonly to just the one location where it didn't error out to do so.
using System;
using Android.Content;
using Android.Support.V7;
using Android.Support.V7.Widget;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Views.Accessibility;
using Android.Widget;
using Java.Util.Concurrent.Atomic;
/**
* https://stackoverflow.com/questions/60764344/how-to-convert-listener-from-java-to-c-sharp
*
* <p>This class is used to create a multiple-exclusion scope for a set of radio
* buttons. Checking one radio button that belongs to a radio group unchecks
* any previously checked radio button within the same group.</p>
* <p/>
* <p>Intially, all of the radio buttons are unchecked. While it is not possible
* to uncheck a particular radio button, the radio group can be cleared to
* remove the checked state.</p>
* <p/>
* <p>The selection is identified by the unique id of the radio button as defined
* in the XML layout file.</p>
* <p/>
* <p>See
* {#link android.widget.GridLayout.LayoutParams GridLayout.LayoutParams}
* for layout attributes.</p>
*
* #see AppCompatRadioButton
*/
public class RadioGridGroup : Android.Support.V7.Widget.GridLayout
{
private static readonly AtomicInteger sNextGeneratedId = new AtomicInteger(1);
private int mCheckedId = -1;
private CompoundButton.IOnCheckedChangeListener mChildOnCheckedChangeListener;
private bool mProtectFromCheckedChange = false;
private OnCheckedChangeListener mOnCheckedChangeListener;
private PassThroughHierarchyChangeListener mPassThroughListener;
public RadioGridGroup(Context context) : base(context)
{
Init();
}
public RadioGridGroup(Context context, IAttributeSet attrs): base(context, attrs)
{
Init();
}
private void Init()
{
mChildOnCheckedChangeListener = new CheckedStateTracker(this);
mPassThroughListener = new PassThroughHierarchyChangeListener(this);
base.SetOnHierarchyChangeListener(mPassThroughListener);
}
public override void SetOnHierarchyChangeListener(IOnHierarchyChangeListener listener)
{
mPassThroughListener.mOnHierarchyChangeListener = listener;
}
protected override void OnFinishInflate()
{
base.OnFinishInflate();
if (mCheckedId != -1)
{
mProtectFromCheckedChange = true;
SetCheckedStateForView(mCheckedId, true);
mProtectFromCheckedChange = false;
SetCheckedId(mCheckedId);
}
}
public override void AddView(View child, int index, ViewGroup.LayoutParams prs)
{
if (child is AppCompatRadioButton) {
AppCompatRadioButton button = (AppCompatRadioButton)child;
if (button.Checked)
{
mProtectFromCheckedChange = true;
if (mCheckedId != -1)
{
SetCheckedStateForView(mCheckedId, false);
}
mProtectFromCheckedChange = false;
SetCheckedId(button.Id);
}
}
base.AddView(child, index, prs);
}
public void check(int id)
{
if (id != -1 && (id == mCheckedId))
{
return;
}
if (mCheckedId != -1)
{
SetCheckedStateForView(mCheckedId, false);
}
if (id != -1)
{
SetCheckedStateForView(id, true);
}
SetCheckedId(id);
}
private void SetCheckedId(int id)
{
mCheckedId = id;
if (mOnCheckedChangeListener != null)
{
mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
}
}
private void SetCheckedStateForView(int viewId, bool chkd)
{
View checkedView = FindViewById(viewId);
if (checkedView != null && checkedView is AppCompatRadioButton) {
((AppCompatRadioButton)checkedView).Checked = (chkd);
}
}
public int GetCheckedCheckableImageButtonId()
{
return mCheckedId;
}
public void clearCheck()
{
check(-1);
}
public void SetOnCheckedChangeListener(OnCheckedChangeListener listener)
{
mOnCheckedChangeListener = listener;
}
public override void OnInitializeAccessibilityEvent(AccessibilityEvent ev)
{
base.OnInitializeAccessibilityEvent(ev);
ev.ClassName = (this.GetType().Name);
}
public override void OnInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)
{
base.OnInitializeAccessibilityNodeInfo(info);
info.ClassName = (this.GetType().Name);
}
public interface OnCheckedChangeListener
{
void onCheckedChanged(RadioGridGroup group, int checkedId);
}
private class CheckedStateTracker : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
{
readonly RadioGridGroup enclosingClass;
public CheckedStateTracker(RadioGridGroup enclosing)
{
enclosingClass = enclosing;
}
public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
{
if (enclosingClass.mProtectFromCheckedChange)
{
return;
}
enclosingClass.mProtectFromCheckedChange = true;
if (enclosingClass.mCheckedId != -1)
{
enclosingClass.SetCheckedStateForView(enclosingClass.mCheckedId, false);
}
enclosingClass.mProtectFromCheckedChange = false;
int id = buttonView.Id;
enclosingClass.SetCheckedId(id);
}
}
private class PassThroughHierarchyChangeListener : Java.Lang.Object,
ViewGroup.IOnHierarchyChangeListener
{
internal ViewGroup.IOnHierarchyChangeListener mOnHierarchyChangeListener;
readonly RadioGridGroup enclosingClass;
public PassThroughHierarchyChangeListener(RadioGridGroup enclosing)
{
enclosingClass = enclosing;
}
public void OnChildViewAdded(View parent, View child)
{
if (parent == enclosingClass && child is AppCompatRadioButton) {
int id = child.Id;
// generates an id if it's missing
if (id == View.NoId)
{
id = View.GenerateViewId();
child.Id = (id);
}
((AppCompatRadioButton)child).SetOnCheckedChangeListener(
enclosingClass.mChildOnCheckedChangeListener);
}
if (mOnHierarchyChangeListener != null)
{
mOnHierarchyChangeListener.OnChildViewAdded(parent, child);
}
}
public void OnChildViewRemoved(View parent, View child)
{
if (parent == enclosingClass && child is AppCompatRadioButton) {
((AppCompatRadioButton)child).SetOnCheckedChangeListener(null);
}
if (mOnHierarchyChangeListener != null)
{
mOnHierarchyChangeListener.OnChildViewRemoved(parent, child);
}
}
}
public static int GenerateViewId()
{
for (; ; )
{
int result = sNextGeneratedId.Get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.CompareAndSet(result, newValue))
{
return result;
}
}
}
}
To answer your multiple questions:
It is ok in this scenario. You need to first understand what final is used for in Java. Here, you can replace final with readonly for all the three fields/variables.
The conversion isn't accurate. In the first case, you are doing an instance comparison of the parent instance whereas in the second case you are doing the type comparison of the parent instance.
As per this SO post , in C#, the nested class do not hold reference of the enclosing class. So it is basically not possible to do something like RadioGridGroup.this in C#, because even though RadioGridGroup encloses the class where RadiGridGroup.this field is being accessed, the reference to that is not available in the enclosed class. Hence, you cannot refer to any private member of the this instance of the RadioGridGroup class. The solution to this is simple. Just treat RadioGridGroup as any other class inside PassThroughHierarchyChangeListener and pass a reference to the this instance of the RadioGridGroup to PassThroughHierarchyChangeListener constructor. So your code would change to something like
internal class CheckedStateTracker : CompoundButton.IOnCheckedChangeListener {
//...
readonly RadioGridGroup enclosingClass;
//Constructor of CheckedStateTracker
//With this, access the members of RadioGridGroup class with enclosingClass scope
//So mProtectFromCheckedChange becomes enclosingClass.mProtectFromCheckedChange
CheckedStateTracker ( RadioGridGroup enclosing){
enclosingClass = enclosing;
}
}
Finally, you instantiate your mChildOnCheckedChangeListener as the following
//Line 51
mChildOnCheckedChangeListener = new CheckedStateTracker(this);
I'm trying to create a UWP App which allows you to modify the colors of what the camera is showing on the fly. My current code is based on the Effective example by Nikola Metulev, and it modifies the camera view by using a IBasicVideoEffect. But I need to modify the colors while the camera is active (showing more red, for example).
I've searched for days, and I think my problem is that, as an IBasicVideoEffect must be in a separated class, I don't know how to pass a value (a Matrix5x4 in this case) from the MainPage to the Effect class.
Here is my code:
MainPage Class:
using ColorEffects;
using System;
using Windows.Media.Capture;
using Windows.Media.Effects;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace Color
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
private MediaCapture _mediaCapture;
public MainPage()
{
this.InitializeComponent();
}
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
_mediaCapture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings()
{
StreamingCaptureMode = StreamingCaptureMode.Video,
};
await _mediaCapture.InitializeAsync(settings);
captureElement.Source = _mediaCapture;
await _mediaCapture.StartPreviewAsync();
var effect = await _mediaCapture.AddVideoEffectAsync(
new VideoEffectDefinition(typeof(TheEffect).FullName),
MediaStreamType.VideoPreview);
}
}
}
ColorEffect Class:
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Brushes;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.Text;
using System;
using System.Collections.Generic;
using System.Numerics;
using Windows.Foundation.Collections;
using Windows.Graphics.DirectX.Direct3D11;
using Windows.Media.Effects;
using Windows.Media.MediaProperties;
using Windows.UI.Text;
namespace ColorEffects
{
public sealed class TheEffect : IBasicVideoEffect
{
private IPropertySet _configuration;
private CanvasDevice _canvasDevice;
public void ProcessFrame(ProcessVideoFrameContext context)
{
using (var inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(_canvasDevice, context.InputFrame.Direct3DSurface))
using (var drawingSurface = CanvasRenderTarget.CreateFromDirect3D11Surface(_canvasDevice, context.OutputFrame.Direct3DSurface).CreateDrawingSession())
{
var matrix = new Matrix5x4() { M11 = 0.5f, M22 = 0.5f, M33 = 1.0f, M44 = 1.0f, M54 = 0.0f };
var colorEffect = new ColorMatrixEffect()
{
ColorMatrix = matrix,
Source = inputBitmap
};
drawingSurface.DrawImage(colorEffect);
}
}
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
_canvasDevice = CanvasDevice.CreateFromDirect3D11Device(device);
}
public bool IsReadOnly { get { return false; } }
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get
{
var properties = new List<VideoEncodingProperties>();
properties.Add(VideoEncodingProperties.CreateUncompressed("ARGB32", 640, 480));
return properties;
}
}
public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Gpu; } }
public bool TimeIndependent { get { return false; } }
public void DiscardQueuedFrames() { }
public void Close(MediaEffectClosedReason reason)
{
if (_canvasDevice != null)
_canvasDevice.Dispose();
}
public void SetProperties(IPropertySet configuration)
{
_configuration = configuration;
}
}
}
Can anyone help my out? Thank you very much!
With a little adjustment to your code, you can do the color change during video running, till you share the same object as a parameter in effect as well as a source object to change color
i.e.
public class TheEffectParameter
{
public Matrix5x4 ColorMatrix { get; set; }
}
public sealed class TheEffect: IBasicVideoEffect
{
private IPropertySet _configuration;
private CanvasDevice _canvasDevice;
public void ProcessFrame(ProcessVideoFrameContext context)
{
using (var inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(_canvasDevice, context.InputFrame.Direct3DSurface))
using (var drawingSurface = CanvasRenderTarget.CreateFromDirect3D11Surface(_canvasDevice, context.OutputFrame.Direct3DSurface).CreateDrawingSession())
{
Matrix5x4 matrix;
if (ColorParameter == null)
{
//Default if parameter not passed
matrix = new Matrix5x4() { M11 = 0.5f, M22 = 0.5f, M33 = 1.0f, M44 = 1.0f, M54 = 0.0f };
}
else
matrix = ColorParameter.ColorMatrix;
var colorEffect = new ColorMatrixEffect()
{
ColorMatrix = matrix,
Source = inputBitmap
};
drawingSurface.DrawImage(colorEffect);
}
}
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
_canvasDevice = CanvasDevice.CreateFromDirect3D11Device(device);
}
public bool IsReadOnly { get { return false; } }
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get
{
var properties = new List<VideoEncodingProperties>();
properties.Add(VideoEncodingProperties.CreateUncompressed("ARGB32", 640, 480));
return properties;
}
}
public MediaMemoryTypes SupportedMemoryTypes { get { return MediaMemoryTypes.Gpu; } }
public bool TimeIndependent { get { return false; } }
public void DiscardQueuedFrames()
{
}
public void Close(MediaEffectClosedReason reason)
{
if (_canvasDevice != null)
_canvasDevice.Dispose();
}
public void SetProperties(IPropertySet configuration)
{
_configuration = configuration;
}
public TheEffectParameter ColorParameter
{
get
{
if (_configuration != null && _configuration.TryGetValue(nameof(ColorParameter), out object val))
return (TheEffectParameter)val;
return null;
}
}
}
and little Change on the main page :
TheEffectParameter parameter;
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
_mediaCapture = new MediaCapture();
var settings = new MediaCaptureInitializationSettings()
{
StreamingCaptureMode = StreamingCaptureMode.Video,
};
await _mediaCapture.InitializeAsync(settings);
captureElement.Source = _mediaCapture;
await _mediaCapture.StartPreviewAsync();
parameter = new TheEffectParameter {
ColorMatrix = new Matrix5x4() { M11 = 0.5f, M22 = 0.5f, M33 = 1.0f, M44 = 1.0f, M54 = 0.0f }
};
var propertySet = new PropertySet();
propertySet.Add(nameof(TheEffect.ColorParameter), parameter);
var effect = await _mediaCapture.AddVideoEffectAsync(
new VideoEffectDefinition(typeof(TheEffect).FullName,propertySet),
MediaStreamType.VideoPreview);
}
public void ChangeColor(Matrix5x4 matrix)
{
if (parameter == null)
return;
parameter.ColorMatrix = matrix;
}
this might have a bug forgive me for that because I write this code using your's on the fly and haven't tested but it should work
I have a button created programatically
InfoBtn = _utilProvider.FloatingBtn("My Button"); //Returns Floating UIButton
InfoBtn.Frame = new CGRect((ScreenWidth / 2) - 22.5, ScreenHeight - 65, 55, 55);
View.Add(InfoBtn);
I want to add drag and drop capability to it programatically. The problem is, the events hooked up by Xamarin wont let me use Custom Event handlers such as UIEvent in this manner (directly converted from https://www.cocoanetics.com/2010/11/draggable-buttons-labels/ iOS example):
InfoBtn.TouchDragInside += (btn, uievent) =>
{
var button = (UIButton)btn;
var e = (UIEvent)uievent; //ERROR: Can not convert type 'System.EventArgs' to 'UIKit.UIEvent'
// get the touch
UITouch touch = (UITouch)e.TouchesForView(button).AnyObject;
// get delta
CGPoint previousLocation = touch.PreviousLocationInView(button);
CGPoint location = touch.LocationInView(button);
nfloat delta_x = location.X - previousLocation.X;
nfloat delta_y = location.Y - previousLocation.Y;
// move button
button.Center = new CGPoint(button.Center.X + delta_x, button.Center.Y + delta_y);
};
According to the example of Xamarin for Using Touch in iOS https://developer.xamarin.com/guides/ios/application_fundamentals/touch/ios_touch_walkthrough/ they use a Storyboard.
a custom UIViewController is created
partial class TouchViewController : UIViewController
and assigned to the ViewController by setting custom class
<viewController id="18" sceneMemberID="viewController" customClass="TouchViewController">
How can that customClass be set programatically?
I have also tried adding a UIGestureRecognizer
InfoBtn.AddGestureRecognizer(new MyGestureRecognizer());
partial class MyGestureRecognizer : UIGestureRecognizer
{
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
UITouch touch = touches.AnyObject as UITouch;
if (touch != null)
{
// Touch started
}
}
public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
base.TouchesCancelled(touches, evt);
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
base.TouchesEnded(touches, evt);
}
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
base.TouchesMoved(touches, evt);
UITouch touch = touches.AnyObject as UITouch;
if (touch != null)
{
// move the shape
}
}
}
But it only ever enters TouchesBegan method.
I am frustrated by now after trying basically every tutorial found online.
Any help would be much appreciated
Okay, I got it to work using https://github.com/TwoRedCells/UIDragDropGestureRecognizer-Xamarin.iOS
I have modified the class a bit, here is the working code:
in ViewDidLoad():
InfoBtn = _utilProvider.FloatingBtn("My Button"); //Returns Floating UIButton
var dd = new DragDropGestureRecognizer();
dd.Dragging += (object sender, DragDropEventArgs e) =>
{
var view = ((DragDropGestureRecognizer)sender).View;
// Reposition box.
var x = e.ViewWasAt.X + e.Delta.X;
var y = e.ViewWasAt.Y + e.Delta.Y;
view.Center = new CGPoint(x, y);
};
InfoBtn.AddGestureRecognizer(dd);
InfoBtn.TouchUpInside += async (object sender, EventArgs e) =>
{
//Button On Click
};
InfoBtn.Frame = new CGRect((ScreenWidth / 2) - 22.5, ScreenHeight - 65, 55, 55);
View.Add(InfoBtn);
Added a class DragDropGestureRecognizer.cs with the same namespace as my application:
using System;
using CGPoint = System.Drawing.PointF;
using Foundation;
using UIKit;
namespace myNS
{
public class DragDropGestureRecognizer : UIGestureRecognizer
{
public DragDropGestureRecognizer()
{
}
public event EventHandler<DragDropEventArgs> Held;
protected void OnHeld(object sender, DragDropEventArgs e)
{
if (Held != null)
Held(sender, e);
}
public event EventHandler<DragDropEventArgs> Dragging;
protected void OnDragging(object sender, DragDropEventArgs e)
{
if (Dragging != null)
Dragging(sender, e);
}
public event EventHandler<DragDropEventArgs> Dropped;
protected void OnDropped(object sender, DragDropEventArgs e)
{
if (Dropped != null)
Dropped(sender, e);
}
public bool DidDrag { get; private set; }
public CGPoint DownAt { get; private set; }
public CGPoint DragAt { get; private set; }
public CGPoint ViewWasAt { get; private set; }
public CGPoint Delta
{
get { return new CGPoint(DragAt.X - DownAt.X, DragAt.Y - DownAt.Y); }
}
public bool Active { get { return DidDrag; } }
public override UIGestureRecognizerState State
{
get { return base.State; }
set { base.State = value; }
}
private CGPoint TouchPoint { get { return (CGPoint)LocationInView(View.Superview); } }
public override bool CanBePreventedByGestureRecognizer(UIGestureRecognizer preventingGestureRecognizer)
{
return false;
}
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
base.TouchesBegan(touches, evt);
if (NumberOfTouches > 1)
{
State = UIGestureRecognizerState.Failed;
return;
}
OnHeld(this, new DragDropEventArgs(default(UIGestureRecognizerState), DragAt, Delta, ViewWasAt));
DownAt = TouchPoint;
ViewWasAt = (CGPoint)View.Center;
State = UIGestureRecognizerState.Possible;
}
public override void TouchesEnded(NSSet touches, UIEvent evt)
{
base.TouchesEnded(touches, evt);
if (DidDrag)
{
State = UIGestureRecognizerState.Recognized;
OnDropped(this, new DragDropEventArgs(State, DragAt, Delta, ViewWasAt));
}
else
State = UIGestureRecognizerState.Failed;
}
public override void TouchesCancelled(NSSet touches, UIEvent evt)
{
base.TouchesCancelled(touches, evt);
State = UIGestureRecognizerState.Failed;
}
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
base.TouchesMoved(touches, evt);
if (State == UIGestureRecognizerState.Failed)
return;
DragAt = TouchPoint;
if (Distance(DownAt, DragAt) > 30 || DidDrag) //Won't move until dragged further than 30px
{
DidDrag = true;
OnDragging(this, new DragDropEventArgs(State, DragAt, Delta, ViewWasAt));
State = UIGestureRecognizerState.Changed;
}
}
public override void Reset()
{
base.Reset();
State = UIGestureRecognizerState.Possible;
DownAt = CGPoint.Empty;
DragAt = CGPoint.Empty;
DidDrag = false;
}
private float Distance(CGPoint point1, CGPoint point2)
{
var dx = point1.X - point2.X;
var dy = point1.Y - point2.Y;
return (float)Math.Sqrt(dx * dx + dy * dy);
}
}
public class DragDropEventArgs : EventArgs
{
public DragDropEventArgs(UIGestureRecognizerState state, CGPoint point, CGPoint delta, CGPoint viewWasAt)
{
State = state;
Point = point;
Delta = delta;
ViewWasAt = viewWasAt;
}
public UIGestureRecognizerState State { get; private set; }
public CGPoint Point { get; private set; }
public CGPoint Delta { get; private set; }
public CGPoint ViewWasAt { get; private set; }
}
}
Thanks to Yvan Rodrigues - TwoRedCells
Android Equivalent:
private int _xPad, _yPad, _xDelta, _yDelta;
prviate bool _moved;
InfoBtn.Touch += (v, me) => //InfoBtn is a button within a frameLayout
{
int X = (int)me.Event.RawX;
int Y = (int)me.Event.RawY;
switch (me.Event.Action & MotionEventActions.Mask)
{
case MotionEventActions.Down:
_xPad = frameLayout.PaddingLeft;
_yPad = frameLayout.PaddingTop;
_xDelta = X;
_yDelta = Y;
_moved = false;
break;
case MotionEventActions.Up:
if (!_moved)
{
//On Button Click
}
break;
case MotionEventActions.PointerDown:
break;
case MotionEventActions.PointerUp:
break;
case MotionEventActions.Move:
var _x = X - _xDelta;
var _y = Y - _yDelta;
_moved = _moved || Math.Abs(_x) > 100 || Math.Abs(_y) > 100; //100px
if (_moved)
{
var padleft = _x - _xPad;
padleft = padleft + InfoBtn.Width > Resources.DisplayMetrics.WidthPixels ? Resources.DisplayMetrics.WidthPixels - InfoBtn.Width : padleft;
var padtop = _y - _yPad;
padtop = padtop + InfoBtn.Height > Resources.DisplayMetrics.HeightPixels ? Resources.DisplayMetrics.HeightPixels - InfoBtn.Height : padtop;
frameLayout.SetPadding(0, 0, padleft, padtop);
}
break;
}
frameLayout.Invalidate();
};
I'm currently having a issue with the SeekBar class in MonoDroid.
Currently I have extended it like this:
public class TTSeekBar : SeekBar, ITTComponent
{
public TTSeekBar(Context context): base(context)
{
}
private int _min = 0;
public int Min { get { return _min; } set { _min = value;} }
public override int Progress
{
get
{
return base.Progress + _min;
}
set
{
base.Progress = value;
}
}
public override int Max
{
get
{
return base.Max + _min;
}
set
{
base.Max = value + _min;
}
}
public object GetInputData()
{
return (this.Progress + _min).ToString();
}
}
But whenever I try to create a object using TTSeekBar _seekBar = new TTSeekBar(this); (where this is a Activity) I get a Sytem.NotSupportedException thrown at the constructor with the message
Unable to activate instance of type TTApp.TTSeekBar from native
handle 44fdad20
Extending other components of the Android.Widget namespace like this seems to work just fine, so I'm wondering why this one doens't work.
Just tested this on API Level 8 and it seems to work.
using System;
using System.Globalization;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Util;
using Android.Widget;
using Android.OS;
namespace AndroidApplication1
{
[Activity(Label = "AndroidApplication1", MainLauncher = true, Icon = "#drawable/icon")]
public class Activity1 : Activity
{
int count = 1;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// Get our button from the layout resource,
// and attach an event to it
Button button = FindViewById<Button>(Resource.Id.MyButton);
button.Click += delegate { button.Text = string.Format("{0} clicks!", count++); };
var seekbar = new TTSeekBar(this);
var ll = FindViewById<LinearLayout>(Resource.Id.LinearLayout);
ll.AddView(seekbar);
}
}
public class TTSeekBar : SeekBar
{
protected TTSeekBar(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
{
}
public TTSeekBar(Context context) : base(context)
{
}
public TTSeekBar(Context context, IAttributeSet attrs) : base(context, attrs)
{
}
public TTSeekBar(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle)
{
}
private int _min = 0;
public int Min { get { return _min; } set { _min = value; } }
public override int Progress
{
get
{
return base.Progress + _min;
}
set
{
base.Progress = value;
}
}
public override int Max
{
get
{
return base.Max + _min;
}
set
{
base.Max = value + _min;
}
}
public object GetInputData()
{
return (Progress + _min).ToString(CultureInfo.InvariantCulture);
}
}
}
So as I said you just need to implement the right constructors and it should work just fine.
There is an explanation as to why here: MonoDroid: Error when calling constructor of custom view - TwoDScrollView