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.
Related
I am litle stuck here.
So this is my class that i initialize my ObservableCollection:
public class ServicosMenu : BindableObject
{
private Rota _oldSer;
public AppService lo = new AppService();
public ObservableCollection<Rota> ListSer { get; set; }
public ObservableCollection<Rota> OneSer { get; set; }//foi introduido pra testar 1 so servico
public List<Rota> y = new List<Rota>();
public RssRotas rts = new RssRotas();
public CalendarPopUp cal = new CalendarPopUp();
public ServicosMenu()
{
rts.data = cal.datacal;
GetListSerAsync();
ListSer = new ObservableCollection<Rota>();
OneSer = new ObservableCollection<Rota>();
}
public void settt(string value)
{
rts.data = value;
}.....
And in the other class this is the code:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Servicos : ContentPage
{
public Servicos (string data)
{
InitializeComponent();
}
public void ListView_ItemTapped(object sender, ItemTappedEventArgs e)
{
var vm = BindingContext as ServicosMenu;
var ser = e.Item as Rota;
vm.HideOrShowService(ser);
vm.OneSer.Add(ser); //this is where it gives me the error
}
private async Task Button_Clicked_DetalhesAsync(object sender, ItemTappedEventArgs e)
{
await Navigation.PushAsync(new Detalhes());
}
So my problem is that the ObservableCollection is doing a "reset" every time I call the view Details, because it has BindingContext to the class ServicoMenu. So every time I call the view, it calls the constructor and it initialize again the collection and I lost the value that I was trying to add.
How can I fix this so it do not "reset" the value?
Thanks for any help!!
EDIT1:
As you can see my view class Detalhes already got the value of the item selected, the think is to bind with the xaml listview.
This is my xaml:
And in the app nothing...
EDIT2:
and when i touch the button to go to the details page on the app, it do not work and i get this strange think in the output:
Damm, it was hard to understand this, what i did was just 2 things diferent from the EDIT2 up in the question.
1º - I just switch this line in c# class of Detalhes : BindingContext = Rote; (and put it after the "InitializeComponent();" )
2º - in the XAML page of Detalhes, in the part that i have the " ItemsSource="{Binding Name}" it actually work just with the dot, like this: ItemsSource="{Binding .}"
#Bijington, #Ian, #David thanks for the help trying to work this in my head, it maybe nothing of the other world for most experience programmers but i´m just now enter the job market and getting to understand the all dynamicof the xamarin.
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.
I created a ListBoxItem where I have a property Name and override ToString() to give back name. That works nicely when I add new items.
But now I need to force the ListBox to update the labels when I change the name of my ship. I thought Refresh or Update would do that but that doesn't work.
I might be missing something very easy here.
public class ShipListBoxItem
{
public ListBox Parent { get; set; }
public ShipType Ship { get; set; }
public ShipListBoxItem()
{
Ship = new ShipType();
}
public ShipListBoxItem(ShipType st)
{
Ship = st;
}
public override string ToString()
{
return Ship.Name;
}
public void UpdateListBox()
{
Parent.Refresh(); //My problem is here. Update doesn't work either.
}
public static ShipListBoxItem AddToListBox(ListBox lb, ShipType ship)
{
ShipListBoxItem li = new ShipListBoxItem(ship);
li.Parent = lb;
lb.Items.Add(li);
return li;
}
}
If you use a List<T> as the DataSource for the listbox it is pretty easy to have changes to items show up. It also means there is no real reason to have a special class for adding a ShipListBoxItem to a ListBox, your basic Ship class may work:
class ShipItem
{
public enum ShipTypes { BattleShip, Carrier, Destroyer, Submarine, Frigate };
public ShipTypes Ship { get; set; }
public string Name { get; set; }
public ShipItem(string n, ShipTypes st)
{
Name = n;
Ship = st;
}
public override string ToString()
{
return String.Format("{0}: {1}", Ship.ToString(), Name);
}
}
The form related stuff:
private void Form1_Load(object sender, EventArgs e)
{
// add some ships
Ships = new List<ShipItem>();
Ships.Add(new ShipItem("USS Missouri", ShipTypes.BattleShip));
Ships.Add(new ShipItem("USS Ronald Reagan", ShipTypes.Carrier));
lb.DataSource = Ships;
}
private void button1_Click(object sender, EventArgs e)
{
// change a ship name
lb.DataSource = null; // suspend binding
this.Ships[0].Name = "USS Iowa";
lb.DataSource = Ships; // rebind
lb.Refresh();
}
As an alternative, you can also tell the Listbox to use a specific property for the display using DisplayMember:
lb.DataSource = Ships;
lb.DisplayMember = "Name";
This would use the Name property in the listbox instead of the ToString method. If your list is changing a lot, use a BindingList instead. It will allow changes to the list show up in the ListBox as you add them without toggling the DataSource.
Try this
ListBox.RefreshItems()
msdn
EDIT: You can use an extended class like this:
public class FooLisBox : System.Windows.Forms.ListBox
{
public void RefreshAllItems()
{
RefreshItems();
}
}
private void button1_Click(object sender, EventArgs e)
{
(listBox1.Items[0] as ShipListBoxItem).Ship.Name = "AAAA";
listBox1.RefreshAllItems();
}
I managed to solve my problem.
Mostly, thanks Jose M.
I ran into a problem however. RefreshItems() triggers OnSelectedIndexChanged()
so my overridden class looks like this
public class MyListBox : ListBox
{
public bool DoEvents{ get; set; } // Made it public so in the future I can block event triggering externally
public MyListBox()
{
DoEvents = true;
}
public void RefreshAllItems()
{
SuspendLayout();
DoEvents = false;
base.RefreshItems(); // this triggers OnSelectedIndexChanged as it selects the selected item again
DoEvents = true;
ResumeLayout();
}
// I only use this event but you can add all events you need to block
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (DoEvents)
base.OnSelectedIndexChanged(e);
}
}
I'm trying to get a new form (FormAlbum) to open when I click buttonOpenAlbum and have an item selected in the AlbumListBox.
If I just have this in buttonOpenAlbum_Click:
private void buttonOpenAlbum_Click(object sender, EventArgs e)
{
FormAlbum MusicForm = new FormAlbum(this);
MusicForm.ShowDialog();
}
The new from opens without error. However, as soon as I mention "AlbumListBox.SelectedItem" (as in the code belowin Form FormMain), I get a "StackOverflowException was unhandled" at:
public ListBox AlbumListBox
{
get
{ // <-This bracket here is where the error highlights
I don't understand why I'm getting this error, only that it must have something to do with AlbumListBox. What am I doing wrong? Any help is appreciated, thank you.
Form FormMain:
public FormMain()
{
InitializeComponent();
}
private void buttonAddAlbum_Click(object sender, EventArgs e)
{
FormAlbumAC addAlbumForm = new FormAlbumAC(this);
addAlbumForm.ShowDialog();
}
private void buttonOpenAlbum_Click(object sender, EventArgs e)
{
if (AlbumListBox.SelectedItem != null)
{
MessageBox.Show(AlbumListBox.SelectedItem.ToString());
FormAlbum MusicForm = new FormAlbum(this);
MusicForm.ShowDialog();
}
else
{
MessageBox.Show("You need to select an album from the list to open.");
}
}
public static class PublicVars
{
public static List<Album> AlbumList { get; set; }
static PublicVars()
{
AlbumList = new List<Album>(MAX_ALBUMS);
}
}
public ListBox AlbumListBox
{
get
{
return AlbumListBox;
}
}
Look at your property implementation:
public ListBox AlbumListBox
{
get
{
return AlbumListBox;
}
}
It's just calling itself, recursively. It may be easier to see that if we convert it to a method:
public ListBox GetAlbumListBox()
{
return GetAlbumListBox();
}
That's why you've got an overflow. It's not clear what you meant it to do... where did you expect the value to come from? You probably need a variable to back the property. What did you expect to set the value returned?
I'd also strongly discourage the design of the PublicVars class. Aside from the naming, you're basically using global variables - not a good idea. Work out which classes need access to the data, and how to get that data to them appropriately.
I am creating a Custom control in where I am creating a property of the type "List"
Sections is a public class which has 4 properties.
The code in the control looks as follows:
public partial class genericGauge : Control
{
public genericGauge()
{
InitializeComponent();
}
// Stripped out code not needed for this issue question.
private List<Sections> indicators = new List<Sections>();
public List<Sections> Indicators
{
get
{
return indicators;
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Stripped out code not needed for this issue question.
}
}
The Sections Class is as follows:
public class Sections
{
private string header = "Section1";
public string Header
{
get {return header;}
set
{
header = value;
}
}
private float startvalue = 0.0f;
public float StartValue
{
get { return startvalue; }
set
{
startvalue = value;
}
}
private float sweepvalue = 0.0f;
public float SweepValue
{
get { return sweepvalue; }
set
{
sweepvalue = value;
}
}
private Color sectioncolor = new Color();
public Color SectionColor
{
get {return sectioncolor;}
set
{
sectioncolor = value;
}
}
}
Everything seems to work fine except that when I add items to the collection at designtime using the property browsers typeeditor the control is not repainted to reflect what is added to the collection.
When I click outside the control on my testform it is repainted.
Usually with simple properties I would use Invalidate, but this seems not to be possible here.
I also tried with other collection types than List<> where it is allowed to have a set accessor, but Invalidate still wont be called. I assume that it means that the SET is never called.
I know how to get this to work with expandable properties but I have no luck finding how to make this update with collections.
I hope someoone can help me out.
thanks in advance.
Instead of using the class List, use the class ObservableCollection, and use that to get notified when a new section is added or removed from the list.
private ObservableCollection<Sections> indicators = new ObservableCollection<Sections>();
public IList<Sections> Indicators
{
get
{
return indicators;
}
}
public genericGauge()
{
InitializeComponent();
this.indicators.CollectionChanged += this.IndicatorsCollectionChanged;
}
private void IndicatorsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// possibly inspect the NotifyCollectionChangedEventArgs to see if it's a change that should cause a redraw.
// or not.
this.Invalidate();
}
When using your example exactly as it was the Indicators property was not available for editing in the property window. So I made a few changes to it.
I added a new class:
// Added this class to deal with the Sections class
public class SectionObservable : ObservableCollection<Sections>
{
// Added a few methods here for creating a designtime collection if I need to.
}
Then I made the change as you suggested
public genericGauge()
{
InitializeComponent();
this.indicators.CollectionChanged += this.IndicatorsCollectionChanged; // your suggestion
}
And made the property like this instead:
private SectionObservable indicators = new SectionObservable(); // using the SectionObservable class instead
public SectionObservable Indicators // using the SectionObservable class instead
{
get
{
return indicators;
}
}
private void IndicatorsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) // your suggestion
{
this.Invalidate();
}
And now works as a charm.
Thank you very much. I appreciate to see that it IS possible to get help this fast. I like this forum alot.