I am having a Telerik TransitionControl which displays advertisements to to end user. the logic is written in such a way that the ad images will be downloaded asynchronously in the behind. the control will display images as it is available. I am using ObservableCollection to hold the advertisement images.New image information is added to this ObservableCollection when a image is successfully downloaded. However, the Telerik TransitionControl is not getting updated with the new images.
I believe the ObservableCollection does not need the OnNotifyPropertyChanged to be called as it will be called internally
Code is given below
//Inside the AdvertUserControl.xaml.cs
ViewModel vm = new ViewModel();
DataContext = vm;
this.radControl.SetValue(AdRotatorExtensions.AdRotatorExtensions.ItemsSourceProperty, vm.SquareAdsVertical);
//Inside the ViewModel.cs
public ReadOnlyObservableCollection<Advert> SquareAdsVertical
{
get
{
if (AdsManager.VerticalAds == null)
{
return null;
}
return new ReadOnlyObservableCollection<Advert>(AdsManager.VerticalAds);
}
}
// Inside DownloadManager.cs
private static ObservableCollection<Advert> adsToShowVertical = new ObservableCollection<Advert>();
public static ObservableCollection<Advert> VerticalAds
{
get { if (adsToShowVertical != null) return adsToShowVertical;
return null;
}
}
public static void OnDownloadComplete(Object sender, AsyncCompletedEventArgs e)
{
try
{
if(!e.Cancelled)
{
if (e.Error == null)
{
Advert ad = e.UserState as Advert ;
adsToShowVertical.Add(ad );
}
}
I have not used the Telerik controls, but I suspect that if you change the following code in your View Model
public ReadOnlyObservableCollection<Advert> SquareAdsVertical
{
get
{
if (AdsManager.VerticalAds == null)
{
return null;
}
return new ReadOnlyObservableCollection<Advert>(AdsManager.VerticalAds);
}
}
To the following
private ReadOnlyObservableCollection<Advert> _readonlyAds;
public ReadOnlyObservableCollection<Advert> SquareAdsVertical
{
get
{
if (AdsManager.VerticalAds == null)
{
return null;
}
else if (_readonlyAds == null)
{
// Only one instance of the readonly collection is created
_readonlyAds = new ReadOnlyObservableCollection<Advert>(AdsManager.VerticalAds);
}
// Return the read only collection that wraps the underlying ObservableCollection
return _readonlyAds;
}
}
You need to return only one instance of the read only collection created from your observable collection. If you change a value in the Observable list, your control will be refreshed through the readonly collection.
Related
I'm trying to add a contains-like autocomplete to winforms combobox. I've started with Hovhannes Hakobyan's idea from this thread. I had to adjust it a bit because autocomplete didn't know where to search.
Let me start by describing my setup:
I have a 'Part' class and the combobox is to display its 'Name' property (DisplayMember). 'Name' is also where autocomplete should search for items containing given string:
public class Part
{
public int PartId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
In form's code-behind, I'm creating new AutoCompleteBehavior object, that will handle all events for me, and I'm passing the combobox and the list of objects. Although I'm refering to 'Part' class here, I'm trying to build general soltution so I'm using generics where possible:
new AutoCompleteBehavior<Part>(this.cmbPart, parts.Items);
cmbPart.DisplayMember = "Name";
cmbPart.ValueMember = "PartId";
Below is complete AutoCompleteBehavior class:
public class AutoCompleteBehavior<T>
{
private readonly ComboBox comboBox;
private string previousSearchterm;
private T[] originalList;
public AutoCompleteBehavior(ComboBox comboBox, List<T>Items)
{
this.comboBox = comboBox;
this.comboBox.AutoCompleteMode = AutoCompleteMode.Suggest; // crucial otherwise exceptions occur when the user types in text which is not found in the autocompletion list
this.comboBox.TextChanged += this.OnTextChanged;
this.comboBox.KeyPress += this.OnKeyPress;
this.comboBox.SelectionChangeCommitted += this.OnSelectionChangeCommitted;
object[] items = Items.Cast<object>().ToArray();
this.comboBox.DataSource = null;
this.comboBox.Items.AddRange(items);
}
private void OnSelectionChangeCommitted(object sender, EventArgs e)
{
if (this.comboBox.SelectedItem == null)
{
return;
}
var sel = this.comboBox.SelectedItem;
this.ResetCompletionList();
comboBox.SelectedItem = sel;
}
private void OnTextChanged(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(this.comboBox.Text) || !this.comboBox.Visible || !this.comboBox.Enabled)
{
return;
}
this.ResetCompletionList();
}
private void OnKeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == '\r' || e.KeyChar == '\n')
{
e.Handled = true;
if (this.comboBox.SelectedIndex == -1 && this.comboBox.Items.Count > 0
&& this.comboBox.Items[0].ToString().ToLowerInvariant().StartsWith(this.comboBox.Text.ToLowerInvariant()))
{
this.comboBox.Text = this.comboBox.Items[0].ToString();
}
this.comboBox.DroppedDown = false;
// Guardclause when detecting any enter keypresses to avoid a glitch which was selecting an item by means of down arrow key followed by enter to wipe out the text within
return;
}
// Its crucial that we use begininvoke because we need the changes to sink into the textfield Omitting begininvoke would cause the searchterm to lag behind by one character That is the last character that got typed in
this.comboBox.BeginInvoke(new Action(this.ReevaluateCompletionList));
}
private void ResetCompletionList()
{
this.previousSearchterm = null;
try
{
this.comboBox.SuspendLayout();
if (this.originalList == null)
{
this.originalList = this.comboBox.Items.Cast<T>().ToArray();
}
if (this.comboBox.Items.Count == this.originalList.Length)
{
return;
}
while (this.comboBox.Items.Count > 0)
{
this.comboBox.Items.RemoveAt(0);
}
this.comboBox.Items.AddRange(this.originalList.Cast<object>().ToArray());
}
finally
{
this.comboBox.ResumeLayout(true);
}
}
private void ReevaluateCompletionList()
{
var currentSearchterm = this.comboBox.Text.ToLowerInvariant();
if (currentSearchterm == this.previousSearchterm)
{
return;
}
this.previousSearchterm = currentSearchterm;
try
{
this.comboBox.SuspendLayout();
if (this.originalList == null)
{
this.originalList = this.comboBox.Items.Cast<T>().ToArray(); // backup original list
}
T[] newList;
if (string.IsNullOrEmpty(currentSearchterm))
{
if (this.comboBox.Items.Count == this.originalList.Length)
{
return;
}
newList = this.originalList;
}
else
{
newList = this.originalList.Where($"{comboBox.DisplayMember}.Contains(#0)", currentSearchterm).ToArray();
//newList = this.originalList.Where(x => x.ToString().ToLowerInvariant().Contains(currentSearchterm)).ToArray();
}
try
{
// clear list by loop through it otherwise the cursor would move to the beginning of the textbox
while (this.comboBox.Items.Count > 0)
{
this.comboBox.Items.RemoveAt(0);
}
}
catch
{
try
{
this.comboBox.Items.Clear();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
this.comboBox.Items.AddRange(newList.Cast<object>().ToArray()); // reset list
}
finally
{
if (currentSearchterm.Length >= 1 && !this.comboBox.DroppedDown)
{
this.comboBox.DroppedDown = true; // if the current searchterm is empty we leave the dropdown list to whatever state it already had
Cursor.Current = Cursors.Default; // workaround for the fact the cursor disappears due to droppeddown=true This is a known bu.g plaguing combobox which microsoft denies to fix for years now
this.comboBox.Text = currentSearchterm; // Another workaround for a glitch which causes all text to be selected when there is a matching entry which starts with the exact text being typed in
this.comboBox.Select(currentSearchterm.Length, 0);
}
this.comboBox.ResumeLayout(true);
}
}
}
Now, the autocomplete is KIND OF working - it seeks for items containing given string and does it well. The problem is, though, that for some reason combobox's SelectedValue==null and SelectedText="" after an item has been selected in combobox. At the same time SelectedItem contains proper 'Part' object and SelectedIndex also has proper value...
Unfortunately when I'm setting combobox.SelectedValue to some value when I'm filling in the form, none item is selected in the combobox. Also, when I'm trying to get combobox.SelectedValue, it also says null (even though an item is selected). I even tried to manually set SelectedValue based on SelectedItem, but I can't set it (it's still null):
private void OnSelectionChangeCommitted(object sender, EventArgs e)
{
if (this.comboBox.SelectedItem == null)
{
return;
}
var sel = this.comboBox.SelectedItem;
this.ResetCompletionList();
comboBox.SelectedItem = sel;
string valueName = comboBox.ValueMember;
comboBox.ValueMember = "";
comboBox.SelectedValue = typeof(T).GetProperty(valueName).GetValue(sel);
}
I think that maybe it's because I'm not using combobox.DataSource property that I can't set/get SelectedValue/SelectedText, but I might be wrong here. Any ideas are welcome! :)
Setting the combobox style to ComboBoxStyle.DropDownList always returns "" (empty string) as SelectedText (reference source)
public string SelectedText
{
get
{
if (DropDownStyle == ComboBoxStyle.DropDownList)
return "";
return Text.Substring(SelectionStart, SelectionLength);
}
{
// see link
}
}
SelectedValue is a member inherited from ListControl and requires the data to be managed (reference source).
public object SelectedValue {
get
{
if (SelectedIndex != -1 && dataManager != null )
{
object currentItem = dataManager[SelectedIndex];
object filteredItem = FilterItemOnProperty(currentItem, valueMember.BindingField);
return filteredItem;
}
return null;
}
set
{
// see link
}
I managed to get it working with using extension methods and reflection. It's working well, although I'm still hoping to find better solution. I've created extension class:
using System.Linq.Dynamic;
namespace JDE_Scanner_Desktop.Static
{
static class Extensions
{
public static int GetSelectedValue<T>(this ComboBox combobox)
{
return (int)typeof(T).GetProperty(combobox.ValueMember).GetValue(combobox.SelectedItem);
}
public static void SetSelectedValue<T>(this ComboBox combobox, int? selectedValue)
{
if(selectedValue != null)
{
combobox.SelectedItem = combobox.Items.Cast<T>().Where(combobox.ValueMember + $"={selectedValue}").FirstOrDefault();
}
}
}
}
Then I'm setting item to be selected with cmbPart.SetSelectedValue<Part>(this.PartId); and I'm getting selcted item's SelectedValue with cmbPart.GetSelectedValue<Part>();.
Of course I'm open for other solutions!
This is my first experience with ToggleSwitch. What I am trying to achieve is to show different data from the list using different ToggleSwitches.
I have a ListView with multiple TextBlocks and one ToggleSwitch for each row of data.
Then I populate ListView with data from List. (List is populated using class that forsees
public ToggleSwitch Switch {get; set;}
Here is how I try to get ToggleSwitch data from each row:
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
for (int a = 0; a < jointList.Count; a++)
{
jointList[a].Switch = sender as ToggleSwitch;
if (jointList[a].Switch != null)
{
if (jointList[a].Switch.IsOn == true)
{
ToggleTest.Text = jointList[a].ProductId.ToString();
ToggleTest.Visibility = Visibility.Visible;
}
else
{
ToggleTest.Visibility = Visibility.Collapsed;
}
}
}
}
Unfortunately I am getting the same(last added) productId from all of the ToggleSwitches as if they were pointing to same place.
EDIT>
I have rewritten the code as touseef suggested:
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
for (int i = 0; i < jointList.Count; i++)
{
if (jointList[i].Value == true)
{
ToggleTest.Text = jointList[i].ProductId.ToString();
// ToggleTest.Text = jointList[a].ProductId.ToString();
ToggleTest.Visibility = Visibility.Visible;
}
else
{
ToggleTest.Visibility = Visibility.Collapsed;
}
}
}
But now nothing shows up.
EDIT:
Here is another attempt to resolve the problem:
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
foreach (var record in jointList)
{
if (record.Value == true)
{
ToggleTest.Text = record.ProductId.ToString();
ToggleTest.Visibility = Visibility.Visible;
}
else
{
ToggleTest.Visibility = Visibility.Collapsed;
}
}
}
And now only one ToggleSwitch works, the one that corresponds to the last added record (I was pulling ProductId of the jointList).
None of the other ToggleSwitches work. They don't return any data when using the code above.
Please use DataTemplate to populate a listview and within your datatemplate put a toggleswitch, and x:Bind the IsOn value for your toggleswitch with a bool property within your item's class. and to get the correct values in your c# object behind set two way databinding.
basic databinding : https://learn.microsoft.com/en-us/windows/uwp/data-binding/data-binding-quickstart
binding in depth : https://learn.microsoft.com/en-us/windows/uwp/data-binding/data-binding-in-depth
you can bind with Binding or x:Bind the better way to bind in uwp is x:Bind though, see the links I provided you they will help you a lot :)
Instead of looping the list and getting sender as ToggleSwitch which will obviously give u same instance everytime. you should just loop over the list which you bind to the ListView, and then check your item.IsOn property and get ur item.ProductId and do whateer u want to with ur item object. note that this item came from the List of items which u are binding to the ListView. when u set two way databinding with toggleswitch, your item.IsOn property will automatically change when toggleswitch.IsOn changes, so you don't need to get any instance of toggleswitch in ur code.
INotify
in order to get notified about the propertychange and for two way databinding to work properly, you need to inherit your Product class from following class
public class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
and then in your IsOn property setter method, call onpropertychanged event like this.
public class Product : Observable
{
public int ProductId { get; set; }
private bool isOn;
public bool IsOn
{
get { return isOn; }
set { isOn = value; Set(ref isOn, value, nameof(IsOn)); }
}
}
Toggled Event
private void ToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
//assuming productList is a List<Product> which was set as ListView.ItemSource
foreach (var product in productList)
{
if (product.IsOn == true)
{
ToggleTest.Text = product.ProductId.ToString();
ToggleTest.Visibility = Visibility.Visible;
}
else
{
ToggleTest.Visibility = Visibility.Collapsed;
}
}
}
if your problem still isn't solved, I will recommend you to put a simplistic app with this problem on the GitHub repo and share the link in your question so people can have a detailed look at it.
I am having trouble understanding why my databindings do not seem to work with my custom class. I made (hacked) my class extend the Control class to add the databindings functionality but it doesn't actually bind to my custom property.
My code for my custom class is:
public class RadioButtonSet : System.Windows.Forms.Control
{
private Dictionary<System.Windows.Forms.RadioButton, int> buttonList;
private int selectedValue;
public RadioButtonSet()
{
buttonList = new Dictionary<System.Windows.Forms.RadioButton, int>();
}
public void AddButton(System.Windows.Forms.RadioButton button, int buttonValue)
{
if (this.buttonList.ContainsKey(button))
throw new Exception("Button set already contains specified button");
else if (buttonValue <= 0)
throw new Exception("Cannot add specified key to button set");
else if (button == null)
throw new Exception("Parameter button cannot be null");
else
{
button.CheckedChanged += button_CheckedChanged;
this.buttonList.Add(button, buttonValue);
}
}
private void setSelectedButton()
{
this.buttonList.FirstOrDefault(x => x.Value == this.selectedValue).Key.Checked = true;
}
private void button_CheckedChanged(object sender, EventArgs e)
{
System.Windows.Forms.RadioButton btn = sender as System.Windows.Forms.RadioButton;
this.selectedValue = this.buttonList[btn];
}
public int SelectedButton
{
get
{
return selectedValue;
}
set
{
selectedValue = value;
setSelectedButton();
}
}
}
And I try to bind to this class using the following, where rbs_admin is an instance of my custom class:
rbs_admin.DataBindings.Add("SelectedButton", datatable, "admin");
I do not know what information may help so here goes.
I get the information to bind from a datatable which is populated by a data adapter. This custom class is not in it's own file, its part of another static class in my project.
I just dont understand as I created a custom textbox with the same custom property and it binds and works fine.
Any help is much appreciated.
Im talking about something like this:
someListControl.DataSource = datatable;
someListControl.DisplayMember = "someAnotherColumnName"
rbs_admin.DataBindings.Add("SelectedButton", datatable, "admin");
Then, selecting an item from list control will cause your control to update its binding according to the selected item.
So first some code, question in the end.
I've got some objects called machines, they have two properties
public Logs MachineLogs {
get { return _logs; }
set {
_logs = value;
NotifyPropertyChanged ("MachineLogs");
}
}
public ObservableCollection<Part> Parts {
get { return _parts; }
set {
_parts = value;
NotifyPropertyChanged ("Parts");
}
}
MachineLogs looks like that:
public ObservableCollection<Log> Malfunctions {
get {
SortCollection (_malfunctions);
return _malfunctions;
}
set {
_malfunctions = value;
NotifyPropertyChanged ("Malfunctions");
}
}
public ObservableCollection<Log> CompletedTasks {
get {
SortCollection (_completedTasks);
return _completedTasks;
}
set {
_completedTasks = value;
NotifyPropertyChanged ("CompletedTasks");
}
}
public ObservableCollection<LogTemplate> TaskTemplates {
get { return _taskTemplates; }
set {
_taskTemplates = value;
NotifyPropertyChanged ("TaskTemplates");
}
}
Now I clone Machine using serialization within BackgroundWorker and then add it to the map where I store it.
protected override void CloneReady ( object sender, RunWorkerCompletedEventArgs e )
{
var machine = ((Machine) e.Result);
_map.Machines.Add (machine);
var machineControl = new MachineControl (machine, _canvas);
((MainWindow) Application.Current.MainWindow).MapPanel.Selector.ProcessSelection (machineControl);
}
Now here's the problem. Everything works alright with parts collection, when I add items to it or remove (the methods for both collections look completly the same). Exception occurs just when I'm trying to perform any operation on Malfunctions collection.
This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
private void AddNewEntry(object sender, RoutedEventArgs e) {
if (LogList.SelectedLog == null)
{
var source = (ObservableCollection<Log>) LogList.ItemsSource;
Log newLog = new Log (LogTypes.Malfunction, ((Machine) DataContext).MachineLogs.Colors.MalfunctionColor.HexValue)
{
DbAction = Data.DBSavers.DatabaseActions.Create,
MachineId = ((Machine) DataContext).Db.Id
};
source.Insert (0, newLog);
LogList.SelectedLog = newLog;
}
else
{
LogList.SelectedLog = null;
}
}
AddNewEntry is called by UI button, tried Invoking Dispatcher but still no luck. Is there any explaination for such behaviour?
Problem doesn't occur when I skip part with BackgroundWorker. Reason why I cannot skip it is that I need to clone multiple machines (copy/paste stuff) and it can take a while so I don't want to freeze UI.
protected override void ProduceClone ( object sender, DoWorkEventArgs e )
{
var serializer = new XmlSerializer ();
var selector = new MapColorSelector ();
var machine = serializer.SerializeMachine (e.Argument as Machine);
machine.Color = selector.DefaultColor;
machine.Location = _location;
e.Result = machine;
}
I love how this site helps solving problems. Most of the answers come after describing problem and carefully looking into it.
This time it was fault of sorting method. Went for LINQ and it works again, but maybe someone could explain this anyway :]
private void SortCollection (ObservableCollection<Log> collection) {
var sorted = CollectionViewSource.GetDefaultView (collection);
sorted.SortDescriptions.Add (new SortDescription ("Date", ListSortDirection.Ascending));
}
I'm attempting my first Windows Form project, having been entirely web based previously and experiencing some issues. I want to bind a list of objects to a TabControl and have this create the Tabs and then have a databound value accessible from the click event of each tab.
The Object I'm wanting to bind is
public class TreeNodeItem
{
private NTree<string> node;
public TreeNodeItem(NTree<string> node)
{
this.node = node;
}
public string Value
{
get { return this.node.data; }
}
}
The NTree node represents a node in an object that models data in a tree structure. I want to create a tab for each object in the list with the Value property being bound to the Tab Text property. Other posts mention binding to the ItemsSource property of the control, but Visual Studio is not giving me this property.
Any help will be greatly appreciated.
Cheers
Stewart
Okay, I was unaware of that the binding was a must. Although I have never seen something like this being done in a Windows Forms Application, I've decided to create a class that does this for us.
It uses the ObservableCollection<T> to keep track whether an object / property has been changed inside its list.
public class ObservableList<T> : ObservableCollection<T>
{
public ObservableList() : base()
{
CollectionChanged += new NotifyCollectionChangedEventHandler(nObservableCollection_CollectionChanged);
}
public event PropertyChangedEventHandler OnPropertyChanged;
void nObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (OnPropertyChanged != null)
{
OnPropertyChanged(new object[] { e.OldItems, e.NewItems }, null); // Call method to let it change the tabpages
}
}
}
Now, we have to create a helper class that helps us keeping track:
public class TabControlBind
{
public TabControlBind(TabControl tabControl)
{
// Create a new TabPageCollection and bind it to our tabcontrol
this._tabPages = new TabControl.TabPageCollection(tabControl);
}
// Fields
private ObservableList<TreeNodeItem> _treeNodeItems;
private TabControl.TabPageCollection _tabPages;
// Properties
public ObservableList<TreeNodeItem> TreeNodeItems
{
get { return _treeNodeItems; }
set
{
if (_treeNodeItems != value)
{
_treeNodeItems = value;
_treeNodeItems.OnPropertyChanged += OnPropretyChanged;
OnPropretyChanged(null, null);
}
}
}
public TabControl.TabPageCollection TabPages
{
get
{
return this._tabPages;
}
}
// Events
private void OnPropretyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender == null) // If list got set
{
// Remove existing tabpages
this._tabPages.Clear();
// Loop through all items inside the ObservableList object and add them to the Tabpage
foreach (TreeNodeItem _treeNodeItem in this._treeNodeItems)
{
TabPage tabPage = new TabPage() { Text = _treeNodeItem.Value, Tag = _treeNodeItems };
this._tabPages.Add(tabPage);
}
}
else if (sender is object[]) // If only one (or multiple) objects have been changed
{
// Get OldItems and NewItems
object[] changedItems = (object[])sender;
// Remove OldItems
if (changedItems[0] != null)
{
foreach (dynamic oldItems in (IList)changedItems[0])
{
foreach (TabPage tab in this._tabPages)
{
if (tab.Text == oldItems.Value)
{
this._tabPages.Remove(tab);
break;
}
}
}
}
// Add OldItems
if (changedItems[1] != null)
{
foreach (dynamic newItems in (IList)changedItems[1])
{
TabPage tabPage = new TabPage() { Text = newItems.Value, Tag = newItems };
this._tabPages.Add(tabPage);
}
}
}
}
}
This is a sample on how to use it:
TabControlBind tabControlBinder;
ObservableList<TreeNodeItem> treeNodeItems;
private void btnAdd_Click(object sender, EventArgs e)
{
// This will automatically update the TabControl
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test3" }));
}
private void frmMain_Load(object sender, EventArgs e)
{
// Create a new list object an add items to it
treeNodeItems = new ObservableList<TreeNodeItem>();
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test" }));
treeNodeItems.Add(new TreeNodeItem(new NTree<string>() { data = "Test2" }));
// Create a new instance of the TabControlBind class, set it to our TabControl
tabControlBinder = new TabControlBind(tabControl);
tabControlBinder.TreeNodeItems = treeNodeItems;
}