UWP - GridView without Xaml templates - c#

Long story short.
I have an UWP UI which contains a GridView and does not use Xaml. I'd like to display fully code-behind constructed items. No Xaml templates.
I have figured out that the GridView's ChoosingItemContainer event will allow me to create the GridViewItem instances programmatically and even possibly reuse them.
However the custom UI of the items is not actually displayed.
I have noticed that when scrolling a large amount of data the content appears very briefly and then it disappears. I'm guessing that the GridViewItem's Content is being overwritten by some kind of default template. Is there a way to disable this machinery?
More generally speaking, is there a known way to use GridView + Items without Xaml at all?
UPDATE:
Here is a minimal code sample that demonstrates the problem.
Place the CustomGridView somewhere in your UI.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
namespace MyApp
{
// Some kind of data object
public class MyData
{
public string MyProperty;
}
// A custom GridViewItem
public class MyGridViewItem : GridViewItem
{
private TextBox mTextBox;
public MyGridViewItem()
{
mTextBox = new TextBox();
mTextBox.Width = 100;
mTextBox.Height = 100;
Content = mTextBox;
// Make the items visible at all: use red background
Background = new SolidColorBrush(Color.FromArgb(255,255,0,0));
}
public void SetData(MyData d)
{
mTextBox.Text = d.MyProperty;
// Content seems to be always reset to the data object itself.
Content = mTextBox;
// With the following line the contents appear briefly while the view is scrolling.
// Without this line the contents don't appear at all
Template = null;
}
}
// Custom grid. No Xaml.
public class CustomGridView : GridView
{
public CustomGridView()
{
this.ChoosingItemContainer += CustomGridView_ChoosingItemContainer;
// Create some data to show.
CollectionViewSource s = new CollectionViewSource();
ObservableCollection<MyData> oc = new ObservableCollection<MyData>();
for(int i = 0;i < 10000;i++)
{
MyData d = new MyData();
d.MyProperty = i.ToString();
oc.Add(d);
}
s.Source = oc;
ItemsSource = s.View;
}
private void CustomGridView_ChoosingItemContainer(ListViewBase sender,ChoosingItemContainerEventArgs args)
{
// Unchecked cast, but for the sake of simplicity let's assume it always works.
MyData d = (MyData)args.Item;
MyGridViewItem it = null;
if((args.ItemContainer != null) && (args.ItemContainer.GetType() == typeof(MyGridViewItem)))
it = (MyGridViewItem)args.ItemContainer;
else
it = new MyGridViewItem();
it.SetData(d);
args.ItemContainer = it;
args.IsContainerPrepared = true;
// This should probably go elsewhere, but for simplicity it's here :)
((ItemsWrapGrid)ItemsPanelRoot).ItemWidth = 100;
((ItemsWrapGrid)ItemsPanelRoot).ItemHeight = 100;
}
}
}

For you can custom UI by new a style to set.
You can set the resource in xaml,and give it a key ,like "res".
And you can get GridView.Style=(Style)Resources["res"]; in code.
To use style in xaml is a good way.But you can set style in code see:
Style style = new Style(typeof (GridView));
style.Setters.Add(new Setter(GridView.Property, xx));
GridView.Style=style ;
The style can be set the GridView and Items just like the xaml.
You have no say that what ui you want that I cant give you a code.

Related

Custom ComboBox: prevent designer from adding to Items

I have a custom combobox control that is supposed to show a list of webcams available.
The code is fairly tiny.
using System;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using DirectShowLib;
namespace CameraSelectionCB
{
public partial class CameraComboBox : ComboBox
{
protected BindingList<string> Names;
protected DsDevice[] Devices;
public CameraComboBox()
{
InitializeComponent();
Devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
Names = new BindingList<string>(Devices.Select(d => d.Name).ToList());
this.DataSource = Names;
this.DropDownStyle = ComboBoxStyle.DropDownList;
}
}
}
However, I ran into a couple of bugs.
First, whenever I place an instance of this combobox, designer generates the following code:
this.cameraComboBox1.DataSource = ((object)(resources.GetObject("cameraComboBox1.DataSource")));
this.cameraComboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cameraComboBox1.Items.AddRange(new object[] {
"HP Webcam"});
Which results in an exception during runtime, since Items should not be modified when DataSource is set. This happens even if I don't touch Items property in the designer.
"HP Webcam" is the only camera present on my computer at the time.
How can I suppress this behaviour?
When you drop your control on a form the constructor code and any loading code will run. Any code in there that changes a property value will be executed in designtime and therefore will be written in the designer.cs of the form you dropped your control on.
When programming controls you should always keep that in mind.
I solved this by adding a property that I can use to check if the code executed in designtime or runtime.
protected bool IsInDesignMode
{
get { return DesignMode || LicenseManager.UsageMode == LicenseUsageMode.Designtime; }
}
protected BindingList<string> Names;
protected DsDevice[] Devices;
public CameraComboBox()
{
InitializeComponent();
if (InDesignMode == false)
{
// only do this at runtime, never at designtime...
Devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
Names = new BindingList<string>(Devices.Select(d => d.Name).ToList());
this.DataSource = Names;
}
this.DropDownStyle = ComboBoxStyle.DropDownList;
}
Now the binding will only happen at runtime
Dont forget to remove the generated code in the Designer.cs file when you try this
The problem is that the binding in constructor is being run by the designer. You could try moving it to the Initialise or Loaded events

Xamarin.Android in C# Visual Studio: Multiple Spinners using the very same adapter?

I have a layout that I want to inflate multiple times to an Alertdialog. I have a list to hold the inflated views and I created an Adapter from a resource defined in Resources/values/Strings.xml.
This is the relevant part of Strings.xml:
<string name="day_prompt">Choose a day</string>
<string-array name="days_array">
<item>Monday</item>
<item>Tuesday</item>
<item>Wednesday</item>
<item>Thursday</item>
<item>Friday</item>
<item>Saturday</item>
<item>Sunday</item>
</string-array>
And here is the code used to create an adpter and a list:
//create a list to hold Views
List<View> chooseElements = new List<View>();
//create a new adapter to be fed with data defined in vals/Strings
var adapter = ArrayAdapter.CreateFromResource(this, Resource.Array.days_array, Android.Resource.Layout.SimpleSpinnerItem);
//set its source to prev mentioned
adapter.SetDropDownViewResource(Android.Resource.Layout.SimpleSpinnerDropDownItem);
for (int i = 0; i < numberOfDays; i++)
{
//creating a custom number of elements
SetUpItems(Resource.Id.dayPicker, Resource.Layout.ChoosingDay, alertHolder, adapter, chooseElements);
}
I want to find the id of a Spinner element inside the actual (latest) ViewGroup element in the list, which I manage to do. My problem is, however, that I can only set the adapter of the Spinner created first, all the other elements appear not the have an adapter attached, as if one adapter can only be used for only one element.
Here is the relevant code:
void SetUpItems(int idOfSpinner, int idOfElement, View alertDialogHolder, ArrayAdapter ad, List<View> list)
{
list.Add(LayoutInflater.Inflate(idOfElement, alertDialogHolder.FindViewById<LinearLayout>(Resource.Id.mainHolder)));
#region Spinner
Spinner spinner = new Spinner(this);
//spinner = alertDialogHolder.FindViewById<Spinner>(idOfSpinner);
spinner = list[list.Count - 1].FindViewById<Spinner>(idOfSpinner);
spinner.ItemSelected += new EventHandler<AdapterView.ItemSelectedEventArgs>(Spinner_ItemSelected);
//var adapter = ArrayAdapter.CreateFromResource(this, Resource.Array.days_array, Android.Resource.Layout.SimpleSpinnerItem);
//adapter.SetDropDownViewResource(Android.Resource.Layout.SimpleSpinnerDropDownItem);
spinner.Adapter = ad;
#endregion
}
I'm fairly new to Xamarin, so I don't really know how to handle multiple Spinners with the same adapter (or if it is actually possible). I tried many solutions to no avail and I could not find any resource on handling multiple Spinners with the same adapter. Could someone give me some advice on this?
In case anyone has the same problem in the future, I found a solution. It is not very clean, but it works. Sorry, I'm not a very experienced developer.
The main problem seems to have been with the following code:
spinner = list[list.Count - 1].FindViewById<Spinner>(idOfSpinner);
which is not problematic per se, but if you want to assign the view found by FindViewById to other elements as well, you gonna face problems, as all the generated elements will have the same ID. For some reason, this does not count as an error (not even a warning) so your program will compile and solve the issue by always returning the very first element with the requested ID from the list. In practical terms, this means that in programs like mine, the very same first element will get re-defined over and over again, while the other elements are ignored.
The problem can be solved if you assign a new ID programmarically to your element. In my code, I used lists so that I can access each element for future data transfer to another activity, but I guess it is possible to do it w/o it, the key element in assigning a new unique ID.
list.Add(LayoutInflater.Inflate(idOfElement, alertDialogHolder.FindViewById<LinearLayout>(Resource.Id.mainHolder)));
//creating a temp list for spinners
List<Spinner> spinners = new List<Spinner>();
//adding a new element
spinners.Add(new Spinner(this));
//capturing the current element, this way, I dont have to re-call it every time
int currentElement = spinners.Count - 1;
//assign a new lineear layout from another list
spinners[currentElement] = list[list.Count - 1].FindViewById<Spinner>(idOfSpinner);
//IMPORTANT: generate a new UNIQUE id
spinners[currentElement].Id = View.GenerateViewId();
//subscribe to the event
spinners[currentElement].ItemSelected += new EventHandler<AdapterView.ItemSelectedEventArgs>(Spinner_ItemSelected);
//set the adapter
spinners[currentElement].Adapter = ad;
Why dont you use ActionSheet? Follow this link you will have a solution on what you looking for.
Have a look the code sample
using Android.Widget;
using Android.OS;
using Xamarin.ActionSheet;
using Android.Support.V4.App;
using System.Collections.Generic;
using Android.App;
namespace ActionSheetTest
{
[Activity (Label = "ActionSheetTest", MainLauncher = true, Icon = "#mipmap/icon")]
public class MainActivity : FragmentActivity,ActionSheet.IActionSheetListener
{
protected override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
// 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 {
var actionSheet = new ActionSheet ();
actionSheet.Other_Button_Title = new List<string> (){ "Option1", "Option2" };
actionSheet.SetActionSheetListener (this);
actionSheet.Show (SupportFragmentManager);
};
}
public void onDismiss (ActionSheet actionSheet, bool isCancel)
{
System.Console.WriteLine ("onDismiss");
}
public void onOtherButtonClick (ActionSheet actionSheet, int index)
{
System.Console.WriteLine (index);
}
}
}

ComboBox.Item.AddRange(string[]) VS. ComboBox.DataSource = string[]

So, I have two options:
aComboBox.Item.AddRange(stringArray);
aComboBox.SelectedItem = stringFromStringArray;
and
aComboBox.DataSource = stringArray;
aComboBox.SelectedItem = stringFromStringArray;
Now, the first one is waaaaay slower when it comes to initialization (about 5-6 times). It does set the selected item properly, but still, it's very slow so I decided to go with the second one.
But, if I use the second one, the Items array within the aComboBox is not yet set when the second command is executed, so the selected item is the one at index 1, instead the one specified.
The question is, how do I get the performance of the second one with the functionality of the first one?
EDIT:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ComboBoxTest
{
class MainWindow : Form
{
string[] list = new string[1548];
TableLayoutPanel panel = new TableLayoutPanel();
public MainWindow() : base()
{
Height = 2000;
Width = 1000;
Random rand = new Random();
for (int i = 0; i < 1547; i++)
{
list[i] = rand.Next().ToString();
}
list[1547] = 5.ToString();
Button button = new Button();
button.Text = "Press me";
button.Click += Button_Click;
panel.Controls.Add(button, 0, 0);
panel.Height = 2000;
panel.Width = 1000;
Controls.Add(panel);
Show();
}
private void Button_Click(object sender, EventArgs e)
{
for (int i = 0; i < 36; i++)
{
ComboBox box = new ComboBox();
box.DataSource = list; //box.Items.AddRange(list);
box.SelectedItem = 5.ToString();
panel.Controls.Add(box, 0, i+1);
}
}
}
}
I reproduced the problem with this program. If you change it to addRange(), it will take a lot more time, but it will set the item.
Try adding a breakpoint to the SelectedItem and then look at the ComboBox.
If you set the one, the other will be null (DataSource vs. Items). The ComboBox seems to look into Items to check if the string exists within the list, and that's why it fails with DataSource method.
Bonus question: Why are all ComboBoxes working as one (try changing the value)?
The question is, how do I get the performance of the second one with
the functionality of the first one?
If you want it to work properly, you can move the line box.SelectedItem = 5.ToString(); to the line after adding boxes to panel.
When you use DataSource for your combo box, setting SelectedItem works only if your combo box exists on your form.
I'm not sure about performance, but sure about functionality.
Bonus question: Why are all ComboBoxes working as one (try changing
the value)?
Because they are bound to the same DataSource. In fact they are using a single BindingManagerBase.
You can use different BindingSource for them. Also you can bind them to list.ToList().
Use teh DataSource method and use the FindString method to find the index of your desired selected text:
string[] arrString = {"hello", "how r u", "fine" };
comboBox1.DataSource = arrString;
comboBox1.SelectedIndex=comboBox1.FindString("fine");
MessageBox.Show(comboBox1.SelectedIndex.ToString());

How to set different forecolor to single label?

lblOperationType.Text = "Text";
Label l1 = new Label();
int len = lblOperationType.Text.Length - 1;
string b = Convert.ToString(lblOperationType.Text.ToCharArray()[0]);
string a = lblOperationType.Text.Substring(1, len);
l1.Text = (b);
l1.ForeColor = Color.Red;
lblOperationType.Text = l1.Text + a;
Is this correct code? I have to make a single label Text like 1st Letter should be red in color.
No, in general (i.e. without custom rendering yourself) a label only has a single foreground colour. Assuming this is Windows Forms, it sounds like you might want a RichTextBox instead - that allows multiple colours, fonts etc within a single control.
As an example:
using System;
using System.Drawing;
using System.Windows.Forms;
class Test
{
static void Main()
{
var rtb = new RichTextBox {
Text = "Test",
ReadOnly = true
};
rtb.Select(1, 3);
rtb.SelectionColor = Color.Red;
rtb.DeselectAll();
var form = new Form { Controls = { rtb } };
Application.Run(form);
}
}
That's not terribly nice code - it would be better to set the Rtf property directly, with the control codes necessary to set the colour, but I'm having a tricky time getting the exact format of the RTF right.

Setting controls in a Telerik GridView cell

Here's a concrete example of what I am attempting to do with the Telerik GridView control. Let's say I have an application that will read a delimited file (say CSV) which will have n columns. The value n can vary from file-to-file but is constant within a file. The application starts with an empty grid and adds columns as required to match the input file. The grid displays all data as strings in all cells. This works with either binding to a BindingList or putting the record (objects) into the GridView.Items list.
Now, what I want to do is put a single row at the top of the grid (a row that will not scroll) that contains comboboxes. That is, at the top of each column, the first row contains a combobox. On the first pass, the combobox will only be a drop list, but on the next pass I will add another row with a set of comboboxes that will be editable. For now, let's only consider drop lists.
The specific problem that I have is that I do not see how to set a specific type of control for a particular cell. Telerik provides a GridViewComboBoxColumn class that will define the behavior for an entire column but that's not what I need.
Because of the variable number of columns, I think that the code-behind would be the place to work this magic. I may have to do something in the xaml but, since I've only been in WPF for a few months, nothing is jumping out at me.
I've done something like this with the DataGridView and XtraGrid, but this one has me stumped. Pointers would be much appreciated.
In response to Jonathan D's answer, I have taken the provided code and modified it to recognize when a cell on the 0th row is being edited. When this is the case, a drop list is presented when the user initiates an edit operation.
using Telerik.Windows.Controls;
using Telerik.Windows.Controls.GridView;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace LLamasoft.DataGuru.Plugin.Internal.ConfigurationUI
{
public class RadGridViewComboboxHeaderColumn : GridViewBoundColumnBase
{
public static readonly DependencyProperty SelectedStringProperty =
DependencyProperty.Register("SelectedString", typeof(string), typeof(RadGridViewComboboxHeaderColumn), new PropertyMetadata(null));
public string SelectedString
{
get { return (string) GetValue(SelectedStringProperty); }
set { SetValue(SelectedStringProperty, value); }
}
public override FrameworkElement CreateCellEditElement(GridViewCell cell, object dataItem)
{
// we need the row on which this cell lives
GridViewDataControl gridViewDataControl = cell.ParentRow.GridViewDataControl;
object currentEditItem = gridViewDataControl.Items.CurrentEditItem;
int index = gridViewDataControl.Items.IndexOf(currentEditItem);
FrameworkElement frameworkElement = null;
if (index == 0)
{
BindingTarget = ComboBox.SelectedValueProperty;
ComboBox comboBox = new ComboBox();
// seed some values,
// this list should be set right after construction if static, otherwise via callback if dynamic
comboBox.Items.Add(string.Empty);
comboBox.Items.Add("apples");
comboBox.Items.Add("oranges");
if (!comboBox.Items.Contains(cell.Value))
{
comboBox.Items.Add(cell.Value);
}
comboBox.SelectedValue = SelectedString;
frameworkElement = comboBox;
}
else
{
BindingTarget = TextBox.TextProperty;
TextBox textBox = new TextBox();
textBox.Text = SelectedString;
frameworkElement = textBox;
}
frameworkElement.SetBinding(BindingTarget, CreateValueBinding());
return frameworkElement;
}
public override object GetNewValueFromEditor(object editor)
{
// ensure that the control will return the correct value when queried for it
ComboBox comboBox = editor as ComboBox;
if (comboBox != null)
{
// bound to comboBox.SelectedValue which carries the correct value
}
TextBox textBox = editor as TextBox;
if (textBox != null)
{
// bound to textBox.Text which carries the correct value
}
return base.GetNewValueFromEditor(editor);
}
private Binding CreateValueBinding()
{
Binding valueBinding = new Binding();
valueBinding.Mode = BindingMode.TwoWay;
valueBinding.NotifyOnValidationError = true;
valueBinding.ValidatesOnExceptions = true;
valueBinding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
valueBinding.Path = new PropertyPath(this.DataMemberBinding.Path.Path);
return valueBinding;
}
}
}
The good news is that this shows that any edit control can be used in any cell based on requirements.
The bad parts are: 1) a dummy record has to be inserted at the 0th list position and must be maintained there, 2) the data is being stored back into the field on the 0th record and may require a different data type than is on the equivalent fields on the other records, and 3) the combobox is only shown when the cell is in the edit mode.
The latter issue for me may not be an issue elsewhere. I want a visual cue that the user is expected to interact with the cells at the top of the columns. Using this method, there is no differentiating factor between the top row and the rest of the rows until the edit operation begins. My ideal solution would have the cells always show their comboboxes.
One other issue that I find difficult to believe that I am facing is the fact that I cannot easily pin/freeze topmost rows. I want this line to always remain at the top after scrolling. There is no _grid.Rows[0].IsPinned = true functionality.
Telerik has responded to my request for info and suggests that I use a template selector to determine how the cell is represented. (http://www.telerik.com/community/forums/wpf/gridview/need-just-first-row-in-grid-to-be-all-comboboxes.aspx#1820310). At this point, I turn my attention to testing that method.
You want to create your own column
using System.Windows;
using System.Windows.Data;
using Telerik.Windows.Controls;
using Telerik.Windows.Controls.GridView;
using System;
namespace Inspect
{
public class DateTimePickerColumn : GridViewBoundColumnBase
{
public TimeSpan TimeInterval
{
get
{
return (TimeSpan) GetValue(TimeIntervalProperty);
}
set
{
SetValue(TimeIntervalProperty, value);
}
}
public static readonly DependencyProperty TimeIntervalProperty =
DependencyProperty.Register("TimeInterval", typeof(TimeSpan), typeof(DateTimePickerColumn), new PropertyMetadata(TimeSpan.FromHours(1d)));
public override FrameworkElement CreateCellEditElement(GridViewCell cell, object dataItem)
{
this.BindingTarget = RadDateTimePicker.SelectedValueProperty;
RadDateTimePicker picker = new RadDateTimePicker();
picker.IsTooltipEnabled = false;
picker.TimeInterval = this.TimeInterval;
picker.SetBinding(this.BindingTarget, this.CreateValueBinding());
return picker;
}
public override object GetNewValueFromEditor(object editor)
{
RadDateTimePicker picker = editor as RadDateTimePicker;
if (picker != null)
{
picker.DateTimeText = picker.CurrentDateTimeText;
}
return base.GetNewValueFromEditor(editor);
}
private Binding CreateValueBinding()
{
Binding valueBinding = new Binding();
valueBinding.Mode = BindingMode.TwoWay;
valueBinding.NotifyOnValidationError = true;
valueBinding.ValidatesOnExceptions = true;
valueBinding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
valueBinding.Path = new PropertyPath(this.DataMemberBinding.Path.Path);
return valueBinding;
}
}
}
That is how you create a custom column. If you modify the CreateCellEditElement Method it will let you create custom cells how you like. You should even be able to detect the row number

Categories