xamarin binding byte array to grid labels - xamarin

I want to binding a byte[] to difference label in grid view but it is weird when I change the value in a timer event,the onPropertyChanged() don't call,turns out the label didn't update with the byte[].Pls help me out where is the mistake in my code.I can binding a byte with the code I list,but seems not work with byte[].Thanks!
System.Timers.Timer testtimer;
Grid gridView;
byte[] _recvdata;
byte testbyte;
public byte[] RecvData
{
get
{
return _recvdata;
}
set
{
if (_recvdata != value)
{
_recvdata = value;
OnPropertyChanged("RecvData");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected override void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public DevicePage(IDevice currDevice)
{
device = currDevice;
this.Title = "Status";
testtimer = new System.Timers.Timer();
testtimer.Interval = 1000;
testtimer.Elapsed += OnTimedEvent;
testtimer.Enabled = true;
gridView = new Grid();
RecvData = new byte[40];
for (byte i = 0; i < 40;i++)
{
string st;
var lb = new Label ();
st = "RecvData[" + i + "]";
lb.SetBinding(Label.TextProperty, st);
lb.BindingContext = this;
gridView.Children.Add(lb, 0, i);
}
this.Content = gridView;
}
private void OnTimedEvent(object sender, System.Timers.ElapsedEventArgs e)
{
byte i;
testbyte += 1;
for (i = 0; i < 40; i++)
{
RecvData[i] = testbyte;
}
}

The setter of RecvData property is not called when you just change it using [indexer]. You have two options I see:
1) manually call OnPropertyChanged after you change the byte array using indexer:
private void OnTimedEvent(object sender, System.Timers.ElapsedEventArgs e)
{
byte i;
testbyte += 1;
for (i = 0; i < 40; i++)
{
RecvData[i] = testbyte;
}
OnPropertyChanged("RecvData");
}
2) create a wrapper for byte array which would call OnPropertyChanged automatically when you change an array using the indexer:
public class ByteArrayWrapper : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
byte[] _array;
public byte[] Array
{
get
{
return _array;
}
set
{
if (_array != value)
{
_array = value;
OnPropertyChanged("Array");
}
}
}
public byte this[int index]
{
get
{
return _array[index];
}
set
{
_array[index] = value;
OnPropertyChanged("Array");
}
}
protected void OnPropertyChanged(string propertyName)
{
var changed = PropertyChanged;
if (changed != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
then instead of operating on your byte array like this:
public byte[] RecvData
{
// get/set
}
you should use an instance of above class:
public ByteArrayWrapper RecvData
{
// get/set
}
Other code should be just fine. If the binding doesn't work, maybe you'd need to bind to RecvData.Array[i] instead of RecvData[i], please let me know if that works as I'm not able to test right now.

Related

Custom Renderer for Picker in Xamarin.Forms

I want to customize my picker. I created a custom renderer for my picker but I dont know how the customization settings. How can I change the font style and size of the item? and How can I remove the two lines?
public class CustomPickerRenderer : PickerRenderer
{
public CustomPickerRenderer(Context context) : base(context)
{
AutoPackage = false;
}
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
Control.Background = null;
var layoutParams = new MarginLayoutParams(Control.LayoutParameters);
layoutParams.SetMargins(0, 0, 0, 0);
Control.LayoutParameters = layoutParams;
Control.SetPadding(0, 0, 0, 0);
SetPadding(0, 0, 0, 0);
}
}
}
If you want to set the fontSize of the text , you first need to customize a subclass extends from NumberPicker and overwrite the method AddView.
public class TextColorNumberPicker: NumberPicker
{
public TextColorNumberPicker(Context context) : base(context)
{
}
public override void AddView(View child, int index, ViewGroup.LayoutParams #params)
{
base.AddView(child, index, #params);
UpdateView(child);
}
public void UpdateView(View view)
{
if ( view is EditText ) {
//set the font of text
((EditText)view).TextSize = 8;
}
}
}
If you want to remove the lines,you should rewrite the NumberPicker
in Android Custom Renderer
public class MyAndroidPicker:PickerRenderer
{
IElementController ElementController => Element as IElementController;
public MyAndroidPicker()
{
}
private AlertDialog _dialog;
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if (e.NewElement == null || e.OldElement != null)
return;
Control.Click += Control_Click;
}
protected override void Dispose(bool disposing)
{
Control.Click -= Control_Click;
base.Dispose(disposing);
}
private void SetPickerDividerColor(TextColorNumberPicker picker)
{
Field[] fields = picker.Class.GetDeclaredFields();
foreach (Field pf in fields)
{
if(pf.Name.Equals("mSelectionDivider"))
{
pf.Accessible = true;
// set the color as transparent
pf.Set(picker, new ColorDrawable(this.Resources.GetColor(Android.Resource.Color.Transparent)));
}
}
}
private void Control_Click(object sender, EventArgs e)
{
Picker model = Element;
var picker = new TextColorNumberPicker(Context);
if (model.Items != null && model.Items.Any())
{
picker.MaxValue = model.Items.Count - 1;
picker.MinValue = 0;
picker.SetBackgroundColor(Android.Graphics.Color.Yellow);
picker.SetDisplayedValues(model.Items.ToArray());
//call the method after you setting DisplayedValues
SetPickerDividerColor(picker);
picker.WrapSelectorWheel = false;
picker.Value = model.SelectedIndex;
}
var layout = new LinearLayout(Context) { Orientation = Orientation.Vertical };
layout.AddView(picker);
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, true);
var builder = new AlertDialog.Builder(Context);
builder.SetView(layout);
builder.SetTitle(model.Title ?? "");
builder.SetNegativeButton("Cancel ", (s, a) =>
{
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
_dialog = null;
});
builder.SetPositiveButton("Ok ", (s, a) =>
{
ElementController.SetValueFromRenderer(Picker.SelectedIndexProperty, picker.Value);
// It is possible for the Content of the Page to be changed on SelectedIndexChanged.
// In this case, the Element & Control will no longer exist.
if (Element != null)
{
if (model.Items.Count > 0 && Element.SelectedIndex >= 0)
Control.Text = model.Items[Element.SelectedIndex];
ElementController.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
// It is also possible for the Content of the Page to be changed when Focus is changed.
// In this case, we'll lose our Control.
Control?.ClearFocus();
}
_dialog = null;
});
_dialog = builder.Create();
_dialog.DismissEvent += (ssender, args) =>
{
ElementController?.SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
};
_dialog.Show();
}
}
I also used this CustomRenderer which was posted before only instead of overriding it you can change the properties like this.
private void Control_Click(object sender, EventArgs e)
{
Picker model = Element;
var picker = new MyNumberPicker(Context);
if (model.Items != null && model.Items.Any())
{
// set style here
picker.MaxValue = model.Items.Count - 1;
picker.MinValue = 0;
picker.SetBackgroundColor(Android.Graphics.Color.Transparent);
picker.SetDisplayedValues(model.Items.ToArray());
//call the method after you setting DisplayedValues
SetPickerDividerColor(picker);
picker.WrapSelectorWheel = false;
picker.Value = model.SelectedIndex;
// change Text Size and Divider
picker.TextSize = 30;
picker.SelectionDividerHeight = 1;
}

How to assign OnActivityResult values to the Listview?

I have a Listview and Click Events in the respective listItems and need to get result back from the click events and populate on the List.
My BaseAdapter class is
public class RemnantListAdapter : BaseAdapter<InventorySlabs>
{
private PartialInventory context;
private List<InventorySlabs> RemnantList;
public RemnantListAdapter(PartialInventory partialInventory, List<InventorySlabs> remnantList)
{
this.context = partialInventory;
this.RemnantList = remnantList;
}
public override InventorySlabs this[int position]
{
get
{
return RemnantList[position];
}
}
public override int Count
{
get
{
return RemnantList.Count;
}
}
public override long GetItemId(int position)
{
return position;
}
public override int ViewTypeCount
{
get
{
return Count;
}
}
public override int GetItemViewType(int position)
{
return position;
}
private class remnantHolder : Object
{
public Button EditRemnant1;
public TextView Remnant1 = null;
public TextView Rem_StockNMaterial1 = null;
public TextView Rem_Dimensions1 = null;
public TextView Rem_Status1 = null;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
remnantHolder holder = null;
if (convertView == null)
{
convertView = (convertView ?? context.LayoutInflater.Inflate(Resource.Layout.remnant_list, parent, false));
holder = new remnantHolder();
holder.EditRemnant1 = convertView.FindViewById<Button>(Resource.Id.editRemnant1);
holder.Remnant1 = convertView.FindViewById<TextView>(Resource.Id.remnant1);
holder.Rem_StockNMaterial1 = convertView.FindViewById<TextView>(Resource.Id.remnant_mtrl1);
holder.Rem_Dimensions1 = convertView.FindViewById<TextView>(Resource.Id.remnant_dimens1);
holder.Rem_Status1 = convertView.FindViewById<TextView>(Resource.Id.remnant_status1);
try
{
InventorySlabs remnantModel = this.RemnantList[position];
if (remnantModel != null)
{
holder.Remnant1.Text = "#" + remnantModel.ExtSlabNo;
holder.Rem_StockNMaterial1.Text = remnantModel.SellName + "-" + remnantModel.Depth + "-" + remnantModel.Finish;
double sqft = (remnantModel.Width * remnantModel.Height) / 144;
holder.Rem_Dimensions1.Text = "(" + remnantModel.Width.ToString() + " * " + remnantModel.Height.ToString() + ")" + sqft.ToString("N2");
holder.Rem_Status1.Text = remnantModel.Status;
holder.EditRemnant1.Click += delegate (object sender, System.EventArgs args)
{
if (remnantModel != null)
{
Intent intent = new Intent(context, typeof(EditRemnant));
intent.PutExtra("OpenPopType", 2);
intent.PutExtra("SlabNo", remnantModel.ExtSlabNo);
context.StartActivityForResult(intent, 1);
}
};
}
}
catch (Exception ex)
{
var method = System.Reflection.MethodBase.GetCurrentMethod();
var methodName = method.Name;
var className = method.ReflectedType.Name;
MainActivity.SaveLogReport(className, methodName, ex);
}
convertView.Tag = holder;
}
else
{
holder = (remnantHolder)convertView.Tag;
}
return convertView;
}
public void ActivityResult(int requestCode, Result resultCode, Intent data)
{
switch (requestCode)
{
case 1:
if (data != null)
{
try
{
string remSlab = data.GetStringExtra("extSlabNo");
if (remSlab != null)
{
remnantData.Width = double.Parse(data.GetStringExtra("remWidth"));
remnantData.Height = double.Parse(data.GetStringExtra("remHeight"));
double sqft = (remnantData.Width * remnantData.Height) / 144;
Rem_Dimensions.Text = "(" + remnantData.Width.ToString() + " * " + remnantData.Height.ToString() + ")" + sqft.ToString("N2");
}
}
catch (Exception ex)
{
var method = System.Reflection.MethodBase.GetCurrentMethod();
var methodName = method.Name;
var className = method.ReflectedType.Name;
MainActivity.SaveLogReport(className, methodName, ex);
}
}
break;
}
}
}
In my Main Activity
I am calling the Result as
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
remnantListAdapter.ActivityResult(requestCode, resultCode, data);
}
After editing the details in Edit view of the ListItem and close the Popup, I am not knowing how to Pass the Values to the Holder to Update in Listview.
You could pass the positon to the new activity in the click event and pass it back with the result intent.
For example:
holder.EditRemnant1.Click += delegate (object sender, System.EventArgs args)
{
if (remnantModel != null)
{
Intent intent = new Intent(context, typeof(EditRemnant));
intent.PutExtra("OpenPopType", 2);
intent.PutExtra("SlabNo", remnantModel.ExtSlabNo);
intent.PutExtra("Position", position );
context.StartActivityForResult(intent, 1);
}
};
And in the new activity:
protected override void OnCreate(Bundle savedInstanceState)
{
Intent intent = Intent;
var position =intent.GetIntExtra("Position", -1);
base.OnCreate(savedInstanceState);
Intent data = new Intent();
String text = "extSlabNo";
data.PutExtra("extSlabNo", text);
data.PutExtra("remWidth", 10);
data.PutExtra("remHeight", 20);
data.PutExtra("Position", position);
SetResult(Result.Ok, data);
}
Then you could modify the data in the adapter according to the positon value:
public void ActivityResult(int requestCode, Result resultCode, Intent data)
{
switch (requestCode)
{
case 1:
if (data != null)
{
try
{
string remSlab = data.GetStringExtra("extSlabNo");
int position = data.GetIntExtra("Position", -1);
if (remSlab != null && position >= 0)
{
RemnantList[position].Width = data.GetIntExtra("remWidth", -1);
RemnantList[position].Height = data.GetIntExtra("remHeight", -1);
NotifyDataSetChanged();
}
}
catch (Exception ex)
{
var method = System.Reflection.MethodBase.GetCurrentMethod();
var methodName = method.Name;
var className = method.ReflectedType.Name;
MainActivity.SaveLogReport(className, methodName, ex);
}
}
break;
}
And reset the adapter in MainActivity OnActivityResult() method :
public class MainActivity : AppCompatActivity
{
RemnantListAdapter remnantListAdapter;
ListView listView;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_main);
List<InventorySlabs> remnantList = new List<InventorySlabs>();
for(var i = 0; i < 10; i++)
{
InventorySlabs inventorySlabs = new InventorySlabs();
inventorySlabs.Depth = i.ToString();
inventorySlabs.ExtSlabNo = i.ToString();
inventorySlabs.Finish = "Finish" + i.ToString();
inventorySlabs.Height = 200;
inventorySlabs.SellName = "SellName" + i.ToString();
inventorySlabs.Status = "Status" + i.ToString();
inventorySlabs.Width = 400;
remnantList.Add(inventorySlabs);
}
listView = FindViewById<ListView>(Resource.Id.listView1);
remnantListAdapter = new RemnantListAdapter(this, remnantList);
listView.Adapter = remnantListAdapter;
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
remnantListAdapter.ActivityResult(requestCode, resultCode, data);
listView.Adapter = remnantListAdapter;
}
}

xamarin binding variable to label using timer event

I want to binding a variable to a label which as a children in a grid.
the variable changed with a timer event.pls check my code help me found out where i am wrong.I can see the label init value is 0,but it won't update when the variable do.
public class DevicePage : ContentPage
{
IDevice device;
Label testLb = new Label();
System.Timers.Timer testtimer;
Grid gridView;
byte testt { get; set; } = 0;
GetSendData communicate;
private byte _maintext;
public byte MainText
{
get
{
return _maintext;
}
set
{
_maintext = value;
OnPropertyChanged();
}
}
public DevicePage(IDevice currDevice)
{
device = currDevice;
this.Title = "Status";
testtimer = new System.Timers.Timer();
testtimer.Interval = 1000;
testtimer.Elapsed += OnTimedEvent;
testtimer.Enabled = true;
gridView = new Grid();
testLb.Text = testt.ToString();
testLb.SetBinding(Label.TextProperty, ".");
testLb.BindingContext = MainText;
gridView.Children.Add(testLb, 0, 0);
this.Content = gridView;
}
private void OnTimedEvent(object sender, System.Timers.ElapsedEventArgs e)
{
MainText += 1;
}
after I implement INotifyPropertyChanged,testLb won't update either.But I change testLb.SetBinding(Label.TextProperty, "."); testLb.BindingContext = MainText; to testLb.SetBinding(Label.TextProperty, "MainText"); testLb.BindingContext = this; It works!

AutomationPeer.GetChildrenCore () only reports first child to VisualStudio.TestTools

I'm not able to override GetChildrenCore correctly. I use this for a Canvas to get information about it's children (Line, Rectangle).
The output correctly indicates the first child but misses the second. Even though the Canvas already contains both.
Custom Canvas
Custom Line Childs of Canvas parent: 2
Instead it should be like this:
Custom Canvas
Custom Line Childs of Canvas parent: 2
Custom Rectangle Childs of Canvas parent: 2
App side side:
public class ElementAP : FrameworkElementAutomationPeer
{
private FrameworkElement Owner = null;
private Int32 Count = 0;
public ElementAP(FrameworkElement owner, Int32 count) : base (owner)
{
Owner = owner;
Count = count;
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Custom;
}
protected override string GetClassNameCore()
{
return $"{Owner.GetType().Name} Childs of Canvas parent: {Count}";
}
}
public class CanvasAP : FrameworkElementAutomationPeer
{
public CanvasAP(Windows.UI.Xaml.Controls.Canvas owner) : base(owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Custom;
}
protected override string GetClassNameCore()
{
return "Canvas";
}
protected override IList<AutomationPeer> GetChildrenCore()
{
var owner = (Windows.UI.Xaml.Controls.Canvas)Owner;
var list = new List<AutomationPeer> ();
foreach (var child in owner.Children)
{
var peer = new ElementAP(child as FrameworkElement, owner.Children.Count);
list.Add(peer);
}
return list;
}
}
UI Testing side:
private static string WalkTree(UITestControl element, Int32 level = 0)
{
var children = element.GetChildren();
var str = "";
foreach (var c in children)
{
str += GetElementString(c, level);
str += WalkTree(c, level + 1);
}
return str;
}
private static string GetElementString(UITestControl element, Int32 level = 0)
{
var xaml = element as XamlControl;
var str = "";
for (var i = 0; i < level; i++)
str += " ";
str += $"{element.ControlType} {element.ClassName} {element.Name} {xaml?.AutomationId ?? ""}\n";
return str;
}
I finally found an answer. When using a cache for the children`s AutomationPeers it works perfectly.
public class ElementAP : FrameworkElementAutomationPeer
{
public UIElement Element { get { return Owner; } }
public ElementAP(FrameworkElement owner) : base(owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Custom;
}
protected override string GetClassNameCore()
{
return Owner.GetType().Name;
}
}
public class CanvasAP : FrameworkElementAutomationPeer
{
private List<ElementAP> _cachedAutomationPeers = new List<ElementAP>();
public CanvasAP(Windows.UI.Xaml.Controls.Canvas owner) : base(owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Custom;
}
protected override string GetClassNameCore()
{
return "Canvas";
}
protected override IList<AutomationPeer> GetChildrenCore()
{
var owner = (Windows.UI.Xaml.Controls.Canvas)Owner;
if (owner.Children.All(c => c is CanvasA))
return base.GetChildrenCore();
var list = new List<ElementAP>();
foreach (var child in owner.Children)
{
var peer = _cachedAutomationPeers.FirstOrDefault(p => p.Element == child) ?? new ElementAP(child as FrameworkElement);
list.Add(peer);
}
_cachedAutomationPeers = list;
return list.Cast<AutomationPeer>().ToList();
}
}

ObservableCollection only updating on Map Zoom and Move events not adding to Collection!

I have an issue with a collection that I have bound. I have a manual refresh button that pulls some moving pushpins from the server. The server is moving the pins itself. After processing I delete the existing collection and re add it to the Observable Collection. This code works and I have verififed that the contents have been update however the pins only "update" (move on the map) if a Zoom or move of the map has happened!
My class is as follows...
public class MapData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void RaisedPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private GeoCoordinate mapCenter = new GeoCoordinate(50, -1);
public GeoCoordinate MapCenter
{
get { return this.mapCenter; }
set
{
if (this.mapCenter == value) return;
this.mapCenter = value;
this.RaisedPropertyChanged("MapCenter");
}
}
private double zoom = 7.0;
public double Zoom
{
get { return this.zoom; }
set
{
if (this.zoom == value) return;
this.zoom = value;
this.RaisedPropertyChanged("Zoom");
}
}
public ObservableCollection<Plane> pins = new ObservableCollection<Plane>() {
};
public ObservableCollection<Plane> Pins
{
get { return pins; }
}
public void RemovePoints()
{
for (int i = 0; i < pins.Count; i++)
{
pins.RemoveAt(i);
}
pins.Clear();
this.RaisedPropertyChanged("Location");
}
public void AddPoints(List<Plane> Planelist)
{
for (int i = 0; i < Planelist.Count; i++)
{
pins.Add(Planelist[i]);
}
}
private Plane selectedPin;
public Plane SelectedPin
{
get {
return this.selectedPin;
}
set
{
if (this.selectedPin == value) return;
this.selectedPin = value;
this.RaisedPropertyChanged("SelectedPin");
}
}
private LocationCollection routePoints = new LocationCollection();
public LocationCollection RoutePoints
{
get { return routePoints; }
}
}
And it is bound using the following...
<my:MapItemsControl ItemsSource="{Binding Pins}" ItemTemplate="{StaticResource PushedMe}"/>
After speaking with Microsoft it appears that the device will cache URL's and that is actually my issue! Forced no-cache on the server side and issue fixed!

Resources