I want to change my ItemsSource but i set the ObservableCollection in another class. How can i Add something to my ItemsSource if the ObservableCollection isn't there?
in Edit window :
private void manageLayout_Click(object sender, Telerik.Windows.RadRoutedEventArgs e)
{
...
scheduleDetail = assetListClass.GetScheduleDetail(xmlScheduleDetail);
ObservableCollection<LibraryData> scheduleDetailOC = new ObservableCollection<LibraryData>(scheduleDetail);
ManageLayout manageLayoutWin = new ManageLayout();
this.Close();
manageLayoutWin.Show();
manageLayoutWin.ManageLayout_GridView.ItemsSource = scheduleDetailOC;
...
}
if it's in the same class i can just use this code :
scheduleDetailOC.Add(abc);
but what if it's in another class? What should i do in ManageLayout window to change the ItemsSource? i tried this :
ManageLayout_GridView.Items.Add(abc);
and i've got an error :
Operation is not valid while ItemsSource is in use. Access and modify
elements with ItemsControl.ItemsSource instead
First of all you need to store the reference to your collection in your class field/property. To have an access from another classes this property should be public.
public class FirstClass
{
public ObservableCollection<LibraryData> ScheduleDetails { get; private set; }
private void manageLayout_Click(object sender, Telerik.Windows.RadRoutedEventArgs e)
{
...
scheduleDetail = assetListClass.GetScheduleDetail(xmlScheduleDetail);
ScheduleDetails = new ObservableCollection<LibraryData>(scheduleDetail);
ManageLayout manageLayoutWin = new ManageLayout();
this.Close();
manageLayoutWin.Show();
manageLayoutWin.ManageLayout_GridView.ItemsSource = ScheduleDetails;
...
}
}
Now you can manipulate collection by reference to the first class. You can use Dependency injection to save a refernce. If your second class needs to add elements to the first class the simplest way is to take a constructor argument:
public class AnotherClass
{
private readonly FirstClass collectionHolder;
public AnotherClass(FirstClass collectionHolder)
{
this.collectionHolder = collectionHolder;
}
public void AddElement()
{
var newElement = GetNewElement(); // creates element that will be add to the collection
collectionHolder.ScheduleDetails.Add(newElement);
}
}
It will works but not good because now AnotherClass knows all about FirstClass public interface. The other reason is that all classes that have reference to the FirstClass can manipulate public collection.
The good design is to create new interface for your FirstClass that will contains only allowed operations and use it in AnotherClass.
public interface IScheduleDetailsCollectionHolder
{
void AddElement(LibraryData data);
}
public class FirstClass : IScheduleDetailsCollectionHolder
{
private ObservableCollection<LibraryData> scheduleDetails;
private void manageLayout_Click(object sender, Telerik.Windows.RadRoutedEventArgs e)
{
...
scheduleDetail = assetListClass.GetScheduleDetail(xmlScheduleDetail);
scheduleDetails = new ObservableCollection<LibraryData>(scheduleDetail);
ManageLayout manageLayoutWin = new ManageLayout();
this.Close();
manageLayoutWin.Show();
manageLayoutWin.ManageLayout_GridView.ItemsSource = scheduleDetails;
...
}
public void AddElement(LibraryData data)
{
scheduleDetails.Add(data);
}
}
public class AnotherClass
{
private readonly IScheduleDetailsCollectionHolder collectionHolder;
public AnotherClass(IScheduleDetailsCollectionHolder collectionHolder)
{
this.collectionHolder = collectionHolder;
}
public void AddElement()
{
var newElement = GetNewElement(); // creates element that will be add to the collection
collectionHolder.AddElement(newElement);
}
}
The other advice is to use MVVM pattern and data binding that is standard de facto for WPF applications.
Related
There are two ways that I tried to add children to a Grid. One is working but it is not prefered to me because I have ViewModel class. And I want to apply it in that class. So the another way is not working correctly, actually, it is not added as children to Grid. The last way is applied in ViewModel class. Here is two ways that I applided:
1- Working way.
public LaserLib fiber1 = new LaserLib();
private void MarkInit()
{
fiber1.Init(); // that is my specialized laser library.
myGrid.Children.Add(fiber1); // when I do this, I can see the fiber1 condition in grid.
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
MarkInit();
}
But, I have many of this and I want to appy to my viewModel.
What I tried:
2- Not worked. Way 1:
public class ViewModels
{
public ObservableCollection<MarkingManualStatuses> markingManualStatuses { get; set;}
= new ObservableCollection<MarkingManualStatuses(Enumerable.Range(0,4).Select(i => new MarkingManualStatuses()));
}
public class MarkingManualStatuses : INotifyPropertyChanged
{
public LaserLib Tmc { get; set; }
public void TmcInitialize()
{
Tmc = new LaserLib ();
Tmc.Init();
}
}
I run TmcInitialize() function at MainWindow_Onloaded
Everything is fine for now. My LaserLib gets initialize. it works. But, I cannot add it to grid.
Here is where I tried to add at ManualPage:
private void listViewMarkingInfos_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TmcViewerGrid.Children.Clear();
int index = listViewMarkingInfos.SelectedIndex;
ContainerMarkingDockPanel.DataContext = viewModels.markingManualStatuses[index];
TmcViewerGrid.Children.Add(viewModels.markingManualStatuses[index].Tmc); // this is not working
ContainerMarkingDockPanel.Visibility = Visibility.Visible;
}
3. Not worked. Way 2:
I have added
Grid property
in
MarkingManualStatuses
It sounds bad but anyway, I just wanted to see if it works or not.
In viewModel:
public class MarkingManualStatuses : INotifyPropertyChanged
{
public LaserLib Tmc { get; set; }
public Grid myGrid {get;set;}
public void TmcInitialize()
{
Tmc = new LaserLib ();
Tmc.Init();
myGrid = new Grid();
myGrid.Children.Add(Tmc);
}
}
In Manual_Page:
private void listViewMarkingInfos_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
TmcViewerGrid.Children.Clear();
int index = listViewMarkingInfos.SelectedIndex;
ContainerMarkingDockPanel.DataContext = viewModels.markingManualStatuses[index];
TmcViewerGrid = viewModels.markingManualStatuses[index].myGrid; // this is also not working.
ContainerMarkingDockPanel.Visibility = Visibility.Visible;
}
I have tried two ways but it didn't work. Any suggestions to solve this?
And maybe I missed a small point that I forget, I am not sure, but Thank you all.
How to pass a reference to a form object (i.e. TextBox) to a class, so I could specify what text box this instance needs to be working with when creating this instance?
Specific example:
I have a class that processes some text strings. I have few instances of this class.
I also have few text boxes on my form. I have a method in a class that shows some text in a text box. What do I need to do to tell to a specific instance of my class what text box to use when creating this instance? Should be in constructor, something like:
public MyClass (string textString, /ref to a text box/)
This is my class:
public class LogClass
{
private readonly TextBox _textBox;
private string logText;
public string LogText
{
get
{
return logText;
}
set
{
logText = value;
}
}
public void AddToLog(string textString)
{
try
{
if (string.IsNullOrEmpty(textString))
{
throw new ArgumentException("message", nameof(textString));
}
logText = logText+ "\n" + textString;
_textBox.Text = logText;
}
catch (Exception)
{
throw;
}
}
public LogClass(string initialText, TextBox textBox)
{
logText = initialText;
_textBox = textBox;
_textBox.Text = logText;
}
}
And this is my form:
public partial class LogWindow : Form
{
LogClass myLog = new LogClass("this is initial string", logOutputBox);
public LogWindow()
{
InitializeComponent();
}
public string LogTextToPass {
get { return logOutputBox.Text; }
set { logOutputBox.Text = value; }
}
private void buttonWriteLog_Click(object sender, EventArgs e)
{
myLog.AddToLog(inputText.Text);
}
private void logOutputBox_TextChanged(object sender, EventArgs e)
{
}
}
Error CS0236 is on this line:
LogClass myLog = new LogClass("this is initial string", logOutputBox);
Error CS0236 A field initializer cannot reference the non-static field, method, or property 'LogWindow.logOutputBox'
logOutputBox is highlighted
You can pass it in any way you want. A constructor argument seems sensible, if the class isn't useable without a TextBox. In the constructor you can subscribe to the TextBox's events.
class TextBoxHandler
{
private readonly TextBox _textbox;
public TextBoxHandler(TextBox textbox)
{
_textbox = textbox;
_textbox.Click += HandleClick;
}
public void HandleClick(object sender, EventArgs e)
{
//Do something
}
}
It should be as easy as, passing a reference to it in the Constructor
Whenever a class or struct is created, its constructor is called. A
class or struct may have multiple constructors that take different
arguments. Constructors enable the programmer to set default values,
limit instantiation, and write code that is flexible and easy to read.
public class MyLovelyHorse
{
// private field of TextBox to play with internally
private readonly TextBox _textbox;
// constructor
public MyLovelyHorse(TextBox textbox)
{
_textbox = textbox;
}
// some awesome method that does stuff
public void SomeMethodThatDoesStuff()
{
_textbox.Text = "rah";
}
}
Usage
var myLovelyHorse = new MyLovelyHorse(MyTextBox);
// do stuff
myLovelyHorse.SomeMethodThatDoesStuff();
I have 2 classes in the same project, ProjectView and FeatureView. I need to access a BindingSource in one class from another class. I have a kluge in which I make the BindingSource scope internal instead of private. Shame, shame. Is there a better way to do this?.
// ProjectView.cs
public partial class ProjectView : System.Windows.Forms.UserControl {
}
// ProjectView.Designer.cs
partial class ProjectView {
// This should be private
internal System.Windows.Forms.BindingSource bsFeatures;
}
// FeatureView.cs
public partial class FeatureView : System.Windows.Forms.UserControl {
// Get ProjectView
Project currentProject = this._presenter.WorkItem.State["CurrentProject"] as Infrastructure.Interface.Aml.BusinessEntities.Project;
string key = System.String.Concat("Project", currentProject.Id);
this._presenter.WorkItem.State["CurrentProject"] = currentProject;
ProjectView view = _presenter.WorkItem.Items.Get<ProjectView>(key);
// Populate currentProject.Features with ProjectView.bsFeatures.List
currentProject.Features.Clear();
IList featureList = view.bsFeatures.List;
foreach (Feature feature in featureList)
{
currentProject.Features.Add(feature);
}
}
maybe something like that, not sure:
partial class ProjectView
{
// This should be private
private System.Windows.Forms.BindingSource bsFeatures;
public System.Windows.Forms.BindingSource BindingSource
{
get { return bsFeatures; }
}
public void ShareOnlyWith(FeatureView fw)
{
fw.BindingSource = bsFeatures;
}
}
of course we break one of the principles, don't depend on concretions.
public partial class TestConrol : UserControl
{
public TestConrol()
{
InitializeComponent();
}
public override string ToString()
{
return "asd";
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
TestConrol tc1 = new TestConrol();
comboBox1.Items.Add(tc1);
TestConrol tc2 = new TestConrol();
comboBox1.Items.Add(tc2);
}
}
When form loaded, I see combobox has two items with empty names, instead of "asd" :/
But this work if I override ToString() in common class, not derived from anything:
public class TestClass
{
public override string ToString()
{
return "bla bla bla";
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
TestClass tcl = new TestClass();
comboBox1.Items.Add(tcl);
}
}
After that I see in combobox "bla bla bla"
Create a property in you control and map the DisplayMember of the combobox to that property, it should work.
I tried to understand the source code(!). This is not a simple call to ToString().
There's an internal class System.Windows.Forms.Formatter doing some stuff. It eventually creates a converter. This is roughly equivalent to saying:
var conv = System.ComponentModel.TypeDescriptor.GetConverter(tc1.GetType());
where tc1 is the TestContol from your question. Now, had we used the TestClass tcl which doesn't implement any interfaces, this would have given us a converter which would eventually call ToString().
But in this example we use tc1, and it is a System.ComponentModel.IComponent. Our conv therefore becomes a System.ComponentModel.ComponentConverter. It uses the Site of the IComponent. When we say:
string result = conv.ConvertTo(tc1, typeof(string));
and the Site is null, we get the empty string "" you saw in your combo box. Had there been a Site it would have used its Name instead.
To demonstrate that, put the following into your TestControl instance constructor:
public TestConrol()
{
InitializeComponent();
Site = new DummySite(); // note: Site is public, so you can also
// write to it from outside the class.
// It is also virtual, so you can override
// its getter and setter.
}
where DummySite is something like:
class DummySite : ISite
{
public IComponent Component
{
get { throw new NotImplementedException(); }
}
public IContainer Container
{
get { throw new NotImplementedException(); }
}
public bool DesignMode
{
get { throw new NotImplementedException(); }
}
public string Name
{
get
{
return "asd"; // HERE'S YOUR TEXT
}
set
{
throw new NotImplementedException();
}
}
public object GetService(Type serviceType)
{
return null;
}
}
Use comboBox1.Items.Add(tc1.ToString()); instead of comboBox1.Items.Add(tcl);
This worked for me:
comboBox1.FormattingEnabled = false
In your UserControl, add a property, and call it FriendlyName for example, as such
namespace project
{
public partial class CustomUserControl : UserControl
{
public CustomUserControl()
{
InitializeComponent();
}
public String FriendlyName { get => "Custom name"; }
}
}
And then set the DisplayMember property of your ComboBox to "FriendlyName", as such
myComboBox.DisplayMember = "FriendlyName";
And to me, this was a very clean solution and gut tells me it is the intended way to do it.
Long story short, I needed a set of objects with dictionary-like functionality that can be serialized in order to save user data. The original dictionary was a Dictionary class that held an array of Item objects and the amounts of each object 'held' by the user. After finding some recommendations on the internet I tried implmenting my own dictionary-like class from KeyedCollection, but can't seem to add objects to it. Am I adding the objects wrong or is something wrong with my collection?
The 'SerialDictionary' class:
public class SerialDictionary : KeyedCollection<Item, int>
{
protected override int GetKeyForItem(Item target)
{
return target.Key;
}
}
public class Item
{
private int index;
private string attribute;
public Item(int i, string a)
{
index = i;
attribute = a;
}
public int Key
{
get { return index; }
set { index = value; }
}
public string Attribute
{
get { return attribute; }
set { attribute = value; }
}
}
The Main form (that is trying to add the object)
public partial class Form1 : Form
{
SerialDictionary ItemList;
Item orb;
public Form1()
{
InitializeComponent();
ItemList = new SerialDictionary();
orb = new Item(0001, "It wants your lunch!");
orb.Key = 001;
}
private void button1_Click(object sender, EventArgs e)
{
ItemList.Add(orb);
}
}
The error I am receiving when trying to add an object:
The best overloaded method match for 'System.Collections.ObjectModel.Collection.Add(int)' has some invalid arguments
If I throw an int in there it compiles, but I'm trying to get a collection of the Item objects in there...
You have it backwards, it should be:
public class SerialDictionary : KeyedCollection<int, Item>
The key type comes first in the signature, then the item type.