Adding custom cells in Xamarin - c#

I have a UITableView which contains a prototype cell, with components added onto it in the storyboard, and referenced in my CustomCell.h & .m files, with the CustomCell.cs looking like this:
public partial class CustomCell : UITableViewCell
{
public CustomCell(IntPtr handle) : base(handle)
{
}
public UILabel Sender
{
get
{
return senderLabel;
}
set
{
senderLabel = value;
}
}
public UILabel Subject
{
get
{
return subjectLabel;
}
set
{
subjectLabel = value;
}
}
public UILabel Date
{
get
{
return timeLabel;
}
set
{
timeLabel = value;
}
}
public UILabel Preview
{
get
{
return previewLabel;
}
set
{
previewLabel = value;
}
}
public UILabel Initials
{
get
{
return initialsLabel;
}
set
{
initialsLabel = value;
}
}
}
This custom cell is then used in my table's source file, and its GetCell method, to try and add data to the table:
CustomCell cell = tableView.DequeueReusableCell("MailCell") as CustomCell;
cell.Sender.Text = TableItems[indexPath.Row].Sender;
cell.Subject.Text = TableItems[indexPath.Row].Subject;
cell.Preview.Text = TableItems[indexPath.Row].Preview;
cell.Date.Text = TableItems[indexPath.Row].Time;
cell.Initials.Text = "";
When you run the program and try and add the data to the table, it breaks on the second line of the GetCell method with a NullReferenceException, which implies that their is either no label in the cell, or that there is no cell to edit. My prototype's cell has an identifier of "MailCell", and I have tried making a full constructor in the class, which didn't work. I had previously programmatically added in the components into the table's cell's, and that worked perfectly and therefore I can be sure that my code to add the data to the source works as intended, and is not the problem. What else is there possible to test?

It breaks because it can not find an instance of you custom cell for the very first row.
DequeueReusableCell is looking for an instance if it can not find one it returns null
Change your code to:
CustomCell cell = tableView.DequeueReusableCell("MailCell") as CustomCell ?? new CustomCell();

Related

How to add a column to datagridview usercontrol c# winform

I want to design a new datagridview as usercontrol. It will have a public and browsable property that indicates whether this datagridview has a counter column or not. If it is true then add a new DataGridViewColumn named 'Counter' at 0 index of rows.
This is my usercontrol code:
public partial class UniLibDataGridView : DataGridView
{
public UniLibDataGridView()
{
InitializeComponent();
if (_HasCounterColumn)
{
this.Columns.Add("Counter", "Counter");
}
}
private bool _HasCounterColumn;
[Browsable(true)]
[Description("Indicates has Counter Column.")]
[Category("UniLib Tools")]
[DisplayName("Has Counter Column")]
public bool HasCounterColumn
{
get { return _HasCounterColumn; }
set { _HasCounterColumn = value; }
}
}
It couldn't change the value of _HasCounterColumn at design time.
It cannot work because the designer creates the object (calls the constructor) before it sets the HasCounterColumn property.
Try this instead :
public class UniLibDataGridView : DataGridView
{
public UniLibDataGridView()
{
}
[Browsable(true)]
[Description("Indicates has Counter Column.")]
[Category("UniLib Tools")]
[DisplayName("Has Counter Column")]
[DefaultValue(false)]
public bool HasCounterColumn
{
get { return Columns.Contains("Counter"); }
set
{
if (value)
Columns.Add("Counter", "Counter");
else if (Columns.Contains("Counter"))
Columns.Remove("Counter");
}
}
}

Exposing DataGridView's columns property in UserControl doesn't work properly

I put a DataGridView in a UserControl and create a public property in my usercontrol that exposes datagridview's columns property.Here is the sample code:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public DataGridViewColumnCollection MyDataGridColumns
{
get
{
return dataGridView1.Columns;
}
}
}
Then I add UserControl1 in my form and I click on MyDataGridColumns property in property window and add 1 or more columns. The problem happens when I rebuild my solution; All of the columns that I have just added disappear after rebuilding.
Can anyone explain to me why this happens? and how to solve it?
This works for me : I created a specific columns editor as it seems it is impossible to use the default columns editor for any control that does not extend DataGridView.
public partial class UserControl1 : UserControl, IDataGridView
{
public UserControl1()
{
InitializeComponent();
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false)]
public DataGridView DataGridView
{
get { return dataGridView1; }
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Editor(typeof(ExtendedDataGridViewColumnCollectionEditor), typeof(UITypeEditor))]
[MergableProperty(false)]
public DataGridViewColumnCollection MyDataGridColumns
{
get { return dataGridView1.Columns; }
}
}
public interface IDataGridView
{
DataGridView DataGridView { get; }
}
class ExtendedDataGridViewColumnCollectionEditor : UITypeEditor
{
private Form dataGridViewColumnCollectionDialog;
private ExtendedDataGridViewColumnCollectionEditor() { }
private static Form CreateColumnCollectionDialog(IServiceProvider provider)
{
var assembly = Assembly.Load(typeof(ControlDesigner).Assembly.ToString());
var type = assembly.GetType("System.Windows.Forms.Design.DataGridViewColumnCollectionDialog");
var ctr = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance)[0];
return (Form)ctr.Invoke(new object[] { provider });
}
public static void SetLiveDataGridView(Form form, DataGridView grid)
{
var mi = form.GetType().GetMethod("SetLiveDataGridView", BindingFlags.NonPublic | BindingFlags.Instance);
mi.Invoke(form, new object[] { grid });
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (provider != null && context != null)
{
var service = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (service == null || context.Instance == null)
return value;
var host = (IDesignerHost)provider.GetService(typeof(IDesignerHost));
if (host == null)
return value;
if (dataGridViewColumnCollectionDialog == null)
dataGridViewColumnCollectionDialog = CreateColumnCollectionDialog(provider);
//Unfortunately we had to make property which returns inner datagridview
//to access it here because we need to pass DataGridView into SetLiveDataGridView () method
var grid = ((IDataGridView)context.Instance).DataGridView;
//we have to set Site property because it will be accessed inside SetLiveDataGridView () method
//and by default it's usually null, so if we do not set it here, we will get exception inside SetLiveDataGridView ()
var oldSite = grid.Site;
grid.Site = ((UserControl)context.Instance).Site;
//execute SetLiveDataGridView () via reflection
SetLiveDataGridView(dataGridViewColumnCollectionDialog, grid);
using (var transaction = host.CreateTransaction("DataGridViewColumnCollectionTransaction"))
{
if (service.ShowDialog(dataGridViewColumnCollectionDialog) == DialogResult.OK)
transaction.Commit();
else
transaction.Cancel();
}
//we need to set Site property back to the previous value to prevent problems with serializing our control
grid.Site = oldSite;
}
return value;
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
}
This is because you didn't specify the type of the column. You should give the type of the column when adding a column (for example DataGridViewTextBoxColumn or DataGridViewCheckBoxColumn). In your Form1.cs do the following:
public Form1()
{
InitializeComponent();
DataGridViewColumn dgViewColumn = new DataGridViewTextBoxColumn();//Or DataGridViewCheckBoxColumn
dgViewColumn.DataPropertyName = "dgViewColumn";
dgViewColumn.HeaderText = #"dgViewColumn";
dgViewColumn.Name = "dgViewColumn";
userControl11.MyDataGridColumns.Add(dgViewColumn);
}
#Bioukh answer works in VS2019 and somewhat works in VS2022. However, the results of embedding the DataGridView control in my UserControl then adding and editing the Columns using the answer does not enable those Columns to migrate to another instance of the UserControl. For example: Copy/Paste the UserControl and all of the embedded DataGridView's columns disappear from the new copy.
To Work Around this issue I maintain my DataGridView instances as native and use a public DataGridView property in my UserControl with the binding and docking performed in the property setter. I then drop my_UserControl on my form, drop my_DataGridView on my form, and then set my_UserControl.DataGridView = my_DataGridView. This work around preserves the native properties and behaviors associated with the DataGridView.
In my_UserControl, I have a Panel named "GridPanel" and a VScrollBar. I then added the following property:
///<summary>
/// Associates a native DataGridView with this UserControl
/// then sets the DataGridView.Parent to the Panel in this UserControl
/// and sets the DataGridView.Dock to Fill the Panel
///</summary>
public DataGridView? ContainedDataGridView
{
get
{
try
{
// if we have a DataGridView in our Panel then return it
if ((this.GridPanel.Controls.Count == 1)
&& (this.GridPanel.Controls[0] is DataGridView view))
{
return view;
}
}
catch (Exception ex)
{
//// TODO Handle "ContainedDataGridView get error"
}
// Return null if there is no DataGridView or there was an error checking for it.
return null;
}
set
{
try
{
// Clear the panel to prevent adding more than one DataGridView
this.GridPanel.Controls.Clear();
if (value is not null)
{
this.GridPanel.Controls.Add(value);
value.Parent = this.GridPanel;
value.Dock = DockStyle.Fill;
}
// else the panel remains cleared
}
catch (Exception ex)
{
//// TODO Handle "ContainedDataGridView set error"
}
}
}
The above snippet is coded as C# 10, .NET 6, Windows Forms App, UserControl and tested in Visual Studio 2022 version 17.0.3

Edit my control from designer

I created control which contains a label and a textbox next to it:
The hierarchy look like that:
Panel
->Panel
->TextBox
->Label
I want to be able to custom the it on designer like change the textbox and label size and the text of the textbox.
Is there a easier way to do it without the needs to add property for each datamember of each control and calculate the sizes?
I have this code right now:
public override string Text
{
set { this.PhoneLabel.Text = value; }
get { return this.PhoneLabel.Text; }
}
public Size SizePhone
{
set { PhoneTextBox.Size = value; }
get { return PhoneTextBox.Size; }
}
public Size SizeLabel
{
set { PhoneLabel.Size = value; }
get { return PhoneLabel.Size; }
}
public Point LocationLabel
{
set { PhoneLabel.Location = value; }
get { return PhoneLabel.Location; }
}
I want to change the size with the mouse like I design a form
Thank you

Add another dropdown button to own devexpres PopupContainerEdit with same style behaviour

We've got custom PopupContainerEdit that inherits from DevExpress'es PopupContainerEdit. One of our custom features is another dropdown button (EditorButton with kind = ButtonPredefines.Glyph) that acts like the default one except, it opens different PopupContainerControl. Everything works as intended except button's style coloring. The button acts like default button - that means it doesn't support state coloring (checked/unchecked) when dropdown is visible/hidden. I couldn't find any custom draw event/method for EditorButton.
Is it possible to achieve such behaviour? If so, how?
#edit
Simple example of the above situation.
Default PopupContainerEdit looks like image A. When you click on the
button (triangle like), dropdown shows and button goes into checked
state.
Our PopupContainerEdit (that inherits from default) looks like
B.
C, D is coloring example when you hover the button.
E is checked state coloring for default button (it works like that by
DevExpress'es design).
F is our button behaviour - acts like normal button.
G is what we want - checked state coloring for our button
Our approach to create custom button:
string TheToolTipText = "The text";
string OurButtonTag = "TheButton";
Image TheIcon = new Image(); // just example ...
EditorButton customButton = new EditorButton();
customButton.Width = 16;
customButton.Image = TheIcon;
customButton.ToolTip = TheToolTipText;
customButton.Tag = OurButtonTag;
customButton.Kind = ButtonPredefines.Glyph;
this.Properties.Buttons.Add(customButton);
To be honest there's nothing more to show. We're not aware of any custom Draw event or similar things.
There are two properties in RepositoryItemPopupContainerEdit that are responsible for this behavior. Fisrt one is RepositoryItemPopupBase.ActionButtonIndex property. It's value specifying which editor button will open the editor's dropdown window. The second one is RepositoryItemPopupContainerEdit.PopupControl which sets the control to display in the popup window. So, by manipulating with this two properties, you can achieve the desired behavior.
Here is example:
0. RepositoryItemPopupContainerEdit descendant
Because you need to show two different PopupContainerControl
you can create additional properties for each of your controls in your custom RepositoryItem.
public class RepositoryItemCustomEdit1 : RepositoryItemPopupContainerEdit
{
#region Some default stuff for custom repository item (constructors, registration, etc).
static RepositoryItemCustomEdit1() { RegisterCustomEdit1(); }
public const string CustomEditName = "CustomEdit1";
public RepositoryItemCustomEdit1() { }
public override string EditorTypeName { get { return CustomEditName; } }
public static void RegisterCustomEdit1()
{
Image img = null;
EditorRegistrationInfo.Default.Editors.Add(new EditorClassInfo(
CustomEditName,
typeof(CustomEdit1),
typeof(RepositoryItemCustomEdit1),
//For v13.2 you need to use custom ViewInfo class. So, here is CustomEdit1ViewInfo.
//For v15.1 you can use the base PopupContainerEditViewInfo.
typeof(CustomEdit1ViewInfo),
new ButtonEditPainter(),
true,
img));
}
#endregion
#region Hide base PopupContainerControl properties in designer.
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override PopupContainerControl PopupControl
{
get { return base.PopupControl; }
set { base.PopupControl = value; }
}
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override int ActionButtonIndex
{
get { return base.ActionButtonIndex; }
set { base.ActionButtonIndex = value; }
}
#region
#region First PopupContainerControl properties
public int DefaultActionButtonIndex { get; set; }
public PopupContainerControl DefaultPopupControl { get; set; }
#endregion
#region Another PopupContainerControl properties
public int DifferentActionButtonIndex { get; set; }
public PopupContainerControl DifferentPopupControl { get; set; }
#endregion
public override void Assign(RepositoryItem item)
{
BeginUpdate();
try
{
base.Assign(item);
RepositoryItemCustomEdit1 source = item as RepositoryItemCustomEdit1;
if (source == null) return;
DefaultActionButtonIndex = source.DefaultActionButtonIndex;
DefaultPopupControl = source.DefaultPopupControl;
DifferentPopupControl = source.DifferentPopupControl;
DifferentActionButtonIndex = source.DifferentActionButtonIndex;
}
finally
{
EndUpdate();
}
}
}
You can see new properties in your designer:
1. PopupContainerEdit descendant
Now you can use this properties in your custom Edit class.
public class CustomEdit1 : PopupContainerEdit
{
#region Some default stuff for custom edit (constructors, registration, etc).
static CustomEdit1() { RepositoryItemCustomEdit1.RegisterCustomEdit1(); }
public CustomEdit1() { }
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public new RepositoryItemCustomEdit1 Properties { get { return base.Properties as RepositoryItemCustomEdit1; } }
public override string EditorTypeName { get { return RepositoryItemCustomEdit1.CustomEditName; } }
#endregion
protected override bool IsActionButton(EditorButtonObjectInfoArgs buttonInfo)
{
int buttonIndex = Properties.Buttons.IndexOf(buttonInfo.Button);
if (buttonIndex == Properties.DefaultActionButtonIndex ||
buttonIndex == Properties.DifferentActionButtonIndex)
{
//Set the Properties.ActionButtonIndex value according to which button is pressed:
Properties.ActionButtonIndex = buttonIndex;
//Set the Properties.PopupControl according to which button is pressed:
if (buttonIndex == Properties.DefaultActionButtonIndex)
Properties.PopupControl = Properties.DefaultPopupControl;
else
Properties.PopupControl = Properties.DifferentPopupControl;
return true;
}
return false;
}
}
2. PopupContainerEditViewInfo descendant
For v13.2 you need to use custom ViewInfo class for your editor:
public class CustomEdit1ViewInfo : PopupContainerEditViewInfo
{
public CustomEdit1ViewInfo(RepositoryItem item) : base(item) { }
public new RepositoryItemPopupBase Item { get { return base.Item as RepositoryItemCustomEdit1; } }
//Show the pressed state when button is pressed or when popup is open.
protected override bool IsButtonPressed(EditorButtonObjectInfoArgs info)
{
var hitObject = PressedInfo.HitObject as EditorButtonObjectInfoArgs;
return
(hitObject != null && hitObject.Button == info.Button) ||
(IsPopupOpen && Item.ActionButtonIndex == info.Button.Index);
}
}
Result
In the result you will get something like this:
and

MonoTouch.Dialog: Update of Text in an Element

Using MonoTouch.Dialog I add StyledStringElement elements.
There is a background task that retrieves details that need to update the element.Value
Is there a way to force the element to have it's text updated after the element.Value is updated?
Ian
If you want to update this element-by-element then you can use something like:
public class MyStyledStringElement {
public void SetValueAndUpdate (string value)
{
Value = value;
if (GetContainerTableView () != null) {
var root = GetImmediateRootElement ();
root.Reload (this, UITableViewRowAnimation.Fade);
}
}
}
A variation would be to load everything and update once (i.e. iterating on the root.Reload for every Element).
I've added "this.PrepareCell (cell); to the SetValueAndUpdate method and works. But I still thinking in that there is another better option detecting the change of "caption" and calling this.PrepareCell (cell);.
public void UpdateCaption(string caption) {
this.Caption = caption;
Console.WriteLine ("actualizando : " + Caption);
UITableViewCell cell = this.GetActiveCell ();
if (cell != null) {
this.PrepareCell (cell);
cell.SetNeedsLayout ();
}
}
Another approach to update the label would be to derive from StyledStringElement or StringElement and directly refresh the DetailTextLabel within the cell:
class UpdateableStringElement : StringElement
{
public UpdateableStringElement(string name): base (name)
{
}
UILabel DetailText = null;
public void UpdateValue(string text)
{
Value = text;
if (DetailText != null)
DetailText.Text = text;
}
public override UITableViewCell GetCell(UITableView tv)
{
var cell = base.GetCell(tv);
DetailText = cell.DetailTextLabel;
return cell;
}
}
Instead of the Value property you can then use the UpdateValue method:
var element = new UpdateableStringElement("demo");
SomeEventOfYours += delegate {
element.UpdateValue(LocalizedValue);
};

Categories

Resources