Autocomplete with BackgroundWorker does not work - wpf

I'm using AutoCompleteBox from the wpf toolkit and I implement the populating by my own
since there is a lot of data and I want to do the search in a background thread.
this is what heppans when I search for the number 12.
while it should show me 4 results - 12,120,121,122.
What am I doing wrong ?
Guide on msdn that I tried to folow: http://msdn.microsoft.com/en-us/library/system.windows.controls.autocompletebox.populating(VS.95).aspx
XAML:
<Grid>
<Controls:AutoCompleteBox x:Name="txtSearch" Populating="AutoCompleteBox_Populating" Height="30" Background="Beige" />
</Grid>
Code behind:
public partial class Window1 : Window {
private int MAX_NUM_OF_RESULTS = 3;
public List<Person> Persons { get; set; }
public List<string> Results { get; set; }
public Window1() {
InitializeComponent();
Persons = new List<Person>();
for (var i = 0; i < 1000000; i++) {
Persons.Add(new Person { Name = i.ToString() });
}
Results = new List<string>();
}
private void AutoCompleteBox_Populating(object sender, PopulatingEventArgs e) {
e.Cancel = true;
var b = new BackgroundWorker();
b.RunWorkerAsync(txtSearch.SearchText);
b.DoWork += b_DoWork;
b.RunWorkerCompleted += b_RunWorkerCompleted;
}
void b_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
txtSearch.ItemsSource = Results;
txtSearch.PopulateComplete();
}
void b_DoWork(object sender, DoWorkEventArgs e) {
Results.Clear();
var counter = 0;
foreach (var person in Persons) {
if (person.Name.StartsWith(e.Argument.ToString())) {
Results.Add(person.Name);
counter++;
if (counter > MAX_NUM_OF_RESULTS) {
break;
}
}
}
}
}
Class Person:
public class Person {
public string Name;
}

Try change the order to the following
var b = new BackgroundWorker();
b.DoWork += b_DoWork;
b.RunWorkerCompleted += b_RunWorkerCompleted;
b.RunWorkerAsync(txtSearch.SearchText);

Are you certain your search logic is actually executing? If so, are the expected results in Results prior to assigning them to ItemsSource?
I think this:
var b = new BackgroundWorker();
b.RunWorkerAsync(txtSearch.SearchText);
b.DoWork += b_DoWork;
b.RunWorkerCompleted += b_RunWorkerCompleted;
Should be this:
var b = new BackgroundWorker();
b.DoWork += b_DoWork;
b.RunWorkerCompleted += b_RunWorkerCompleted;
b.RunWorkerAsync(txtSearch.SearchText);
Otherwise you risk having the worker start before setting up event handlers.

Related

WPF: store objects inside application settings.settings file

I build a simple ClipboardManager that hold all last Copy item.
So i have this simple ClipboardItem class:
public class ClipboardItem : INotifyPropertyChanged
{
private string _text { get; set; }
private int _index { get; set; }
public string Text
{
get { return _text; }
set
{
_text = value;
NotifyPropertyChanged();
}
}
public int Index
{
get { return _index; }
set
{
_index = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
And my ViewModel class that hold ObservableCollection<ClipboardItem>:
public class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<ClipboardItem> _clipboards;
public ViewModel()
{
if (_clipboards == null)
{
_clipboards = new ObservableCollection<ClipboardItem>();
_clipboards.CollectionChanged += _clipboards_CollectionChanged;
}
}
private void _clipboards_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
for (int i = 0; i < _clipboards.Count; i++)
_clipboards[i].Index = i + 1;
}
public ObservableCollection<ClipboardItem> Clipboards
{
get { return _clipboards; }
set
{
_clipboards = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
So every Copy create new ClipboardItem object inside list but when i restart the application all the records gone so i wonder if there any way to store all my ClipboardItem object inside the application settings.settings file.
//Variable Creation
private string _dataFileName = #"ClipData.xml";
DataTable _clipDataTable = new DataTable();
Inside ViewModel constructor.
public ViewModel()
{
if (_clipboards == null)
{
_clipboards = new ObservableCollection<ClipboardItem>();
_clipboards.CollectionChanged += _clipboards_CollectionChanged;
}
InitDataTable();
ReadDataFile();
}
Create new Methods
/// <summary>
/// Initialize Data Table considering you have only 1 column data.
/// If you have more then you need to create more columns
/// </summary>
private void InitDataTable()
{
_clipDataTable = new DataTable();
_clipDataTable.Columns.Add("ClipHeader");
_clipDataTable.AcceptChanges();
}
//the clipboard Data is saved in xml file.
private void WriteDataFile()
{
DataSet ClipDataSet = new DataSet();
ClipDataSet.Tables.Add(_clipDataTable);
ClipDataSet.WriteXml(_dataFileName);
}
// if file exits then read the xml file and add it to the Collection, which will be reflected in UI.
private void ReadDataFile()
{
DataSet ClipDataSet = new DataSet();
if (File.Exists(_dataFileName))
{
ClipDataSet.ReadXml(_dataFileName);
foreach (DataRow item in ClipDataSet.Tables[0].Rows)
{
Clipboards.Add(new ClipboardItem { Text = Convert.ToString(item["ClipHeader"]) });
}
}
}
Using Command you can bind Method of ViewModel to Window Closing event. So whenever the user closes the window, the data in the collection will be written into the Xml file.
Data from the Collection is copied into DataTable.
private void WindowCloseCommadn(object o)
{
foreach (var item in Clipboards)
{
DataRow dataRow = _clipDataTable.NewRow();
dataRow["ClipHeader"] = item.Text;
_clipDataTable.Rows.Add(dataRow);
}
WriteDataFile();
}
Update:-
Same Code without ViewModel, by making the codebehind class has the DataContext for binding Collection.
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ClipboardMonitor clipboardMonitor;
private string _dataFileName = #"ClipData.xml";
DataTable _clipDataTable = new DataTable();
public ObservableCollection<ClipboardItem> Clipboards { get; set; }
public MainWindow()
{
InitializeComponent();
Clipboards = new ObservableCollection<ClipboardItem>();
Clipboards.CollectionChanged += Clipboards_CollectionChanged;
Loaded += MainWindow_Loaded;
InitiateClipboardMonitor();
this.Closing += MainWindow_Closing1;
this.DataContext = this;
}
private void MainWindow_Closing1(object sender, CancelEventArgs e)
{
foreach (var item in Clipboards)
{
DataRow dataRow = _clipDataTable.NewRow();
dataRow["ClipHeader"] = item.Text;
_clipDataTable.Rows.Add(dataRow);
}
WriteDataFile();
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
InitDataTable();
ReadDataFile();
}
/// <summary>
/// Initialize Data Table considering you have only 1 column data.
/// If you have more then you need to create more columns
/// </summary>
private void InitDataTable()
{
_clipDataTable = new DataTable();
_clipDataTable.Columns.Add("ClipHeader");
_clipDataTable.AcceptChanges();
}
private void WriteDataFile()
{
DataSet ClipDataSet = new DataSet();
ClipDataSet.Tables.Add(_clipDataTable);
ClipDataSet.WriteXml(_dataFileName);
}
private void ReadDataFile()
{
DataSet ClipDataSet = new DataSet();
if (File.Exists(_dataFileName))
{
ClipDataSet.ReadXml(_dataFileName);
foreach (DataRow item in ClipDataSet.Tables[0].Rows)
{
Clipboards.Add(new ClipboardItem { Text = Convert.ToString(item["ClipHeader"]) });
}
}
}
private void Clipboards_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
for (int i = 0; i < Clipboards.Count; i++)
{
Clipboards[i].Index = i + 1;
}
}
private void InitiateClipboardMonitor()
{
clipboardMonitor = new ClipboardMonitor();
clipboardMonitor.OnClipboardContentChanged += ClipboardMonitor_OnClipboardContentChanged; ;
}
private void ClipboardMonitor_OnClipboardContentChanged(object sender, EventArgs e)
{
string clipboardText = Clipboard.GetText(TextDataFormat.Text);
Clipboards.Add(new ClipboardItem { Text = clipboardText });
}
}
to know about Command, refer the article
https://www.codeproject.com/Articles/274982/Commands-in-MVVM

Handling events across different classes using C#

I have a property defined in Class A. When the property is changed then i need to raise an event. Another class B should respond to this event and do something. I have done something like this:
Code:
Class A{
string variable = "test";
public delegate void EventHandler(object sender, EventArgs args);
public static event EventHandler ThrowAddEvent = delegate { };
public static event EventHandler ThrowRemoveEvent = delegate { };
public String Name { get; set; }
public int Select { get; set; }
public Window Formref1 { get; set; }
public bool IsSelected
{
get { return IsSlctd; }
set
{
IsSlctd = value;
if (value)
{
ThrowAddEvent(this, new EventArgs());
}
else
{
ThrowRemoveEvent(this, new EventArgs());
}
}
}
}
Another class which is responding to this event is defined as follows:
Class B{
public B(ParsedResult _result)
{
InitializeComponent();
if (_result != null)
{
this.Result = _result;
PopulateColumns1();
DataGrid1.Items.Clear();
DataGrid1.ItemsSource = Populatevariables();
}
}
public void PopulateColumns1()
{
DataGridTextColumn Colvar = new DataGridTextColumn();
Colvar.Header = "Variables";
Colvar.Binding = new Binding("Name");
DataGrid1.Columns.Add(Colvar);
DataGridTemplateColumn Col = new DataGridTemplateColumn();
Col.Header = "Select";
DataTemplate dd = new DataTemplate();
FrameworkElementFactory FF = new FrameworkElementFactory(typeof(CheckBox));
FF.SetBinding(CheckBox.BindingGroupProperty, new Binding("Select"));
FF.SetBinding(CheckBox.IsCheckedProperty, new Binding("IsSelected") { UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged });
FF.SetValue(FrameworkElement.HorizontalAlignmentProperty, HorizontalAlignment.Center);
dd.VisualTree = FF;
Col.CellTemplate = dd;
DataGrid1.Columns.Add(Col);
}
private List<A> PopulateVariables()
{
CheckBox cb = new CheckBox();
List<A> CharList = new List<A>();
Result.GetCharacteristicList.MoveNext();
IEnumerator<A2LCharacteristic> enumeratorlist = Result.GetCharacteristicList;
for (int i = 0; enumeratorlist.MoveNext(); i++)
{
CharList.Add(new A() { Name = enumeratorlist.Current.Name, Select = i, Formref1 = this});
}
enumeratorlist.Reset();
return CharList;
}
private void OKBtn_Click(object sender, RoutedEventArgs e)
{
A Instance_A = new A();
Instance_A.ThrowAddEvent += (sender1, args) => { Addvrbl(Instance_A.variable); };
Instance_A.ThrowRemoveEvent += (sender2, args) => { RmvVrbl(Instance_A.variable); };
this.Close();
}
public void Addvrbl(string vrbl)
{
if (!(vrbllist.Contains(vrbl)))
{
vrbllist.Add(vrbl);
}
}
public void RmvVrbl(string vrbl)
{
if ((vrbllist.Contains(vrbl)))
{
vrbllist.Remove(vrbl);
}
}
}
The problem is it is not going inside the method "AddVrbl" and "RmvVrbl". I have used the solution from here. Please help.
OK, instead of subscribing to the event of a new instance of A,which will never get triggered/published. When you initializing CharList, you have to subscribe to the event of each A item.
Something like:
for (int i = 0; enumeratorlist.MoveNext(); i++)
{
var obja=new A() { Name = enumeratorlist.Current.Name, Select = i, Formref1 = this};
obja.ThrowAddEvent += ...
obja.ThrowRemoveEvent += ...
CharList.Add(obja);
}
PS: Make sure you un-subscribe these events before removing an item from your CharList.

Subscribing to PropertyChangedEventHandler in Winforms

In the following example the temp variable in RaisePropertyChanged() is always null. How do I subscribe to the event?
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
using System.Runtime.CompilerServices;
using System.Text;
namespace TestProject.Module.BusinessObjects
{
public class ContactPerson : INotifyPropertyChanged
{
public string FirstName { get; set; }
public string LastName { get; set; }
[NotMapped]
public string PersonFullName
{
set
{
if (PersonFullName != value)
{
var stringArray = value.Split();
var firstIndex = 0;
var lastIndex = stringArray.Length - 1;
if (lastIndex >= firstIndex)
{
FirstName = stringArray[firstIndex];
}
if (lastIndex > firstIndex)
{
LastName = stringArray[lastIndex];
}
RaisePropertyChanged();
}
}
get
{
var sb = new StringBuilder();
sb.Append(FirstName);
sb.Append(" ");
sb.Append(LastName);
var stringArray = sb.ToString().Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
var s = string.Join(" ", stringArray);
return s;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ContactPerson Clone()
{
var obj = new ContactPerson { FirstName = FirstName, LastName = LastName };
return obj;
}
public override string ToString()
{
return PersonFullName;
}
protected void RaisePropertyChanged([CallerMemberName] string propertName = "")
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(propertName));
}
}
}
}
from reading This question it seems that PropertyChanged has not been subscribed to. How do I do this subscription?
Like this:
private void Form1_Load(object sender, EventArgs e)
{
ContactPerson p = new ContactPerson();
p.PropertyChanged += P_PropertyChanged;
}
private void P_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
throw new NotImplementedException();
}
EDIT: expanding the sample code:
public partial class Form1 : Form
{
ContactPerson p;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
p = new ContactPerson();
p.PersonFullName = "Mary Jane";
p.PropertyChanged += P_PropertyChanged;
label1.Text = p.PersonFullName;
// If you use databinding instead, you get the same result in this case.
//label1.DataBindings.Add(new Binding("Text", p, "PersonFullName"));
}
private void P_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
label1.Text = p.PersonFullName;
}
private void button1_Click(object sender, EventArgs e)
{
p.PersonFullName = "John Doe";
}
}

Binding In Binding with Templates(WPF)

I have a WPF UI Bound to a collection of AwesomeClass
Now, AwesomeClass has a collection of AwesomeParts objects.
How can I make my UI In such a way that (as an example)
for each AwesomeClass instance, there is a Tab on a tab panel
and then for each awesome part in that, there is an object on a listbox, on that tab.
Basically: Awesomes->Tabs
And Then : Awesome.Awesomeparts->Tabs.Listbox
Following is the code to do what you are looking for :
public partial class TabWindow : Window
{
public TabWindow()
{
InitializeComponent();
List<AwesomeClass> items = new List<AwesomeClass>();
for (int i = 0; i < 10; i++)
{
items.Add(new AwesomeClass());
}
AwesomeTabs.ItemsSource = items;
Loaded += new RoutedEventHandler(TabWindow_Loaded);
}
// Method 1
void TabWindow_Loaded(object sender, RoutedEventArgs e)
{
FindListBox(AwesomeTabs);
}
private void FindListBox(DependencyObject obj)
{
Int32 count = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is ListBox)
{
(child as ListBox).SelectionChanged += new SelectionChangedEventHandler(ListBox_SelectionChanged);
}
else
{
FindListBox(child);
}
}
}
// Method 2
private void ListBox_Loaded(object sender, RoutedEventArgs e)
{
(sender as ListBox).SelectionChanged += new SelectionChangedEventHandler(ListBox_SelectionChanged);
}
void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
MessageBox.Show(e.AddedItems[0].ToString());
}
catch (Exception)
{ }
}
}
class AwesomeClass
{
static Int32 count = 0;
public Int32 Index { get; set; }
public List<AwesomePart> Parts { get; protected set; }
// Method 3 : Preferred
private AwesomePart _selectedPart;
public AwesomePart SelectedPart
{
get { return _selectedPart; }
set
{
OnSelectionChanged(_selectedPart, value);
_selectedPart = value;
}
}
private void OnSelectionChanged(AwesomePart oldValue, AwesomePart newValue)
{
if (newValue != null) MessageBox.Show(newValue.ToString());
}
public AwesomeClass()
{
Index = ++count;
Parts = new List<AwesomePart>();
for (int i = 0; i < 10; i++)
{
Parts.Add(new AwesomePart());
}
}
public override string ToString()
{
return "Tab #" + Index.ToString();
}
}
class AwesomePart
{
static Int32 count = 0;
public Int32 Index { get; set; }
public AwesomePart()
{
Index = ++count;
}
public override string ToString()
{
return "Part #" + Index.ToString();
}
}
XAML:
<Grid>
<TabControl Name="AwesomeTabs">
<TabControl.ContentTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Parts}" SelectedItem="{Binding SelectedPart}" Loaded="ListBox_Loaded"></ListBox>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Bind a List<AwesomeClass> to a headered content control. Each "AwesomeClass" object will be set as the datacontext for each "tab" in the headered content control.
Within the content control that is on each "tab", bind the DataContext (AwesomeClass) property that accesses the List<AwesomePart> to your Listbox control.
Make sense?
Cheers.

real time loop counter ouput wpf

What can I do if I want to have a text-box representing in real time the value of a loop counter in wpf?
appending working solution:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private delegate void UpdateTextBox(DependencyProperty dp, Object value);
...
private void MyMethod()
{
...
int iMax=...;
...
MyClass iMyClass = new MyClass(arguments);
this.DataContext = iMyClass;
UpdateTextBox updateTBox = new UpdateTextBox(textBlock1.SetValue);
for (int i = 1; i <= iMax; i++)
{
iMyClass.MyClassMethod(i);
Dispatcher.Invoke(updateTBox, System.Windows.Threading.DispatcherPriority.Background, new object[] { MyClass.MyPropertyProperty, iMyClass.myProperty });
}
Here is the code I tried according to your suggestion, but it doesnt work, I get "0" written in the textbox, so I suppose the binding is OK, but the loop doesnt work. I also made the loop to write into another textbox directly by textbox2.text="a" inside the loop, but it didnt work either.
/// <summary>
/// Interaction logic for Window1.xaml
/// DOESNT WORK PROPERLY
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TestClass tTest = new TestClass();
this.DataContext = tTest ;
tTest.StartLoop();
}
}
public class TestClass : DependencyObject
{
public TestClass()
{
bwLoop = new BackgroundWorker();
bwLoop.DoWork += (sender, args) =>
{
// do your loop here -- this happens in a separate thread
for (int i = 0; i < 10000; i++)
{
LoopCounter=i;
}
};
}
BackgroundWorker bwLoop;
public int LoopCounter
{
get { return (int)GetValue(LoopCounterProperty); }
set { SetValue(LoopCounterProperty, value); }
}
public static readonly DependencyProperty LoopCounterProperty = DependencyProperty.Register("LoopCounter", typeof(int), typeof(TestClass));
public void StartLoop()
{
bwLoop.RunWorkerAsync();
}
}
}
Run the loop in a background process (so that the UI can update itself while the loop is running) and
write the loop counter into a property (DependencyProperty or CLR property with INotifyPropertyChanged) which is bound to a TextBox in your user interface. Alternatively, you can directly change the value of the TextBox via Dispatcher.Invoke (this is less elegant, though).
Does this help? Feel free to ask for clarification...
Code example (untested), using a DependencyProperty (which must be bound to a TextBox):
BackgroundWorker bwLoop;
public static readonly DependencyProperty LoopCounterProperty =
DependencyProperty.Register("LoopCounter", typeof(int),
typeof(Window1), new FrameworkPropertyMetadata(0));
public int LoopCounter {
get { return (int)this.GetValue(LoopCounterProperty); }
set { this.SetValue(LoopCounterProperty, value); }
}
private MyWindow() {
...
bwLoop = new BackgroundWorker();
bwLoop.DoWork += (sender, args) => {
...
for (int i = 0; i < someLimit; i++) {
Dispatcher.Invoke(new Action(() => LoopCounter=i));
System.Threading.Thread.Sleep(250); // do your work here
}
}
bwLoop.RunWorkerCompleted += (sender, args) => {
if (args.Error != null)
MessageBox.Show(args.Error.ToString());
};
}
private void StartLoop() {
bwLoop.RunWorkerAsync();
}
you can use Data Binding for continuously updating the text.

Resources