I am trying to make a display of my items listed on the right side of my window. When I first initialize my window, everything works perfectly. What I do is, I deserialize a file and get my ObservableCollection<Model.Resources>. All the items from my list are shown in the ListBox.
When I enter my Add Resource window, and add some more resources, I serialize those objects into a file. When I am finished with adding, I call a method called refresh() that deserializes some files and updates my ObservableCollection<Model.Resources>. When I am finished with that, ListBox doesn't update until I restart my program.
XAML of my Window that has ListBox:
<ListBox x:Name="itemList" Grid.Column="1" HorizontalAlignment="Stretch" Margin="7,23,6,0" Grid.Row="1" VerticalAlignment="Stretch" Background="#324251" ItemsSource="{Binding Path=resources}" FontSize="16" Foreground="Wheat"/>
Relevant code of my Window class:
public partial class GlowingEarth : Window, INotifyPropertyChanged
{
private ObservableCollection<Model.Etiquette> _tags;
private ObservableCollection<Model.Resource> _resources;
private ObservableCollection<Model.Type> _types;
public ObservableCollection<Model.Etiquette> tags
{
get
{
return _tags;
}
set
{
_tags = value;
OnPropertyChanged("tags");
}
}
public ObservableCollection<Model.Resource> resources
{
get
{
return _resources;
}
set
{
_resources = value;
OnPropertyChanged("resources");
}
}
public ObservableCollection<Model.Type> types
{
get
{
return _types;
}
set
{
_types = value;
OnPropertyChanged("types");
}
}
private BinaryFormatter fm;
private FileStream sm = null;
private string path;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public GlowingEarth()
{
InitializeComponent();
tags = new ObservableCollection<Model.Etiquette>();
resources = new ObservableCollection<Model.Resource>();
types = new ObservableCollection<Model.Type>();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
refresh();
}
public void refresh()
{
path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "typeList");
if (File.Exists(path))
{
fm = new BinaryFormatter();
sm = File.OpenRead(path);
_types = (ObservableCollection<Model.Type>)fm.Deserialize(sm);
sm.Dispose();
}
else
{
return;
}
path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tagList");
if (File.Exists(path))
{
fm = new BinaryFormatter();
sm = File.OpenRead(path);
_tags = (ObservableCollection<Model.Etiquette>)fm.Deserialize(sm);
sm.Dispose();
}
else
{
return;
}
path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "reslist");
if (File.Exists(path))
{
fm = new BinaryFormatter();
sm = File.OpenRead(path);
_resources = (ObservableCollection<Model.Resource>)fm.Deserialize(sm);
sm.Dispose();
}
else
{
return;
}
InitializeComponent();
this.DataContext = this;
}
Can you please tell me, what on earth is going on, and why isn't my list updating?
in refresh() method you change the value of a field
_resources = (ObservableCollection<Model.Resource>)fm.Deserialize(sm);
it does not call OnPropertyChanged("resources");
you should use resources property
resources = (ObservableCollection<Model.Resource>)fm.Deserialize(sm);
same for _types and _tags
Related
I thought that my bug was a Xamarin Forms issue because there was no bug in XF3.4, but it appeared after I upgraded to XF4.4.
Just to make sure, I want to show you guys the code. I have a XAML page with the loading icon:
<ActivityIndicator IsRunning="{Binding Loading}"
IsVisible="{Binding Loading}"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.2, 0.2">
<ActivityIndicator.Color>
<OnPlatform x:TypeArguments="Color" iOS="#2499CE" WinPhone="#2499CE" />
</ActivityIndicator.Color>
</ActivityIndicator>
The "Loading" boolean is binded in the page model here:
public class MyLoginWebPageModel : BasePageModel
{
private BrowserOptions _options;
private Action<BrowserResult> _trySetResult;
private BrowserResult _result = new BrowserResult() { ResultType = BrowserResultType.UserCancel };
private Boolean _navPopped = false;
public string StartUrl { get; private set; }
public bool Loading { get; set; } = false; // RIGHT HERE!!!!!!!!!!!!!!!!!!!!!!
public OidcLoginWebPageModel(ICoreDataRepository repository, ILoginProvider loginProvider, ICache cache, IEventTrace trace, IUsageTimer usageTimer, IPlatform platform)
: base(loginProvider, cache, trace, usageTimer, platform){}
public override void Init(object initData)
{
base.Init(initData);
Tuple<BrowserOptions, Action<BrowserResult>> initObject = initData as Tuple<BrowserOptions, Action<BrowserResult>>;
_options = initObject.Item1;
_trySetResult = initObject.Item2;
StartUrl = _options.StartUrl;
}
protected override void OnPageWasPopped(object sender, EventArgs e)
{
base.OnPageWasPopped(sender, e);
_trySetResult(_result);
}
internal async Task OnBrowserNavigated(object sender, WebNavigatedEventArgs e)
{
Loading = false;
if (!(sender is WebView browser))
{
throw new Exception($"Sender is not of type WebView");
}
if (!Uri.TryCreate(e.Url, UriKind.Absolute, out Uri uri))
{
throw new Exception($"Uri creation failed for: {e.Url}");
}
if (string.IsNullOrEmpty(_options.EndUrl))
{
if (uri.LocalPath.ToLowerInvariant() == "/account/logout")
{
_result = new BrowserResult() { ResultType = BrowserResultType.Success };
if (!_navPopped)
{
_navPopped = true;
await PopPageModel();
}
}
}
}
internal async Task OnBrowserNavigating(object sender, WebNavigatingEventArgs e)
{
Loading = true;
if (!(sender is WebView browser))
{
throw new Exception($"Sender is not of type WebView");
}
if (!Uri.TryCreate(e.Url, UriKind.Absolute, out Uri uri))
{
throw new Exception($"Uri creation failed for: {e.Url}");
}
if (string.IsNullOrEmpty(_options.EndUrl) == false)
{
if (uri.AbsoluteUri.StartsWith(_options.EndUrl))
{
_result = new BrowserResult() { ResultType = BrowserResultType.Success, Response = uri.Fragment.Substring(1) };
e.Cancel = true;
if (!_navPopped)
{
_navPopped = true;
Loading = false;
await PopPageModel();
}
}
}
}
}
Is there anything in here that would indicate a loading icon not disappearing at all?
Thanks!
edit: So this is what I'm thinking I need to do.
First I change my boolean situation
private bool Loading = false;
public bool currentlyLoading
{
get { return Loading; }
set
{
currentlyLoading = Loading;
onPropertyChanged();
}
}
Then in the same file I implement the onPropertyChanged() function to.. let the Bindable property in my xaml file know that the property has changed?
Is this a good implementation?
// Option 1
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// Option 2
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
your class (or it's base class) needs to implement INotifyPropertyChanged. Then your Loading property would look something like this
private bool loading = false;
public bool Loading
{
get { return loading; }
set
{
loading = value;
OnPropertyChanged();
}
}
Option 1 is best. But it would be good if you move your property changed logic to BasePageModel.
Try this one:
private bool _loading { get; set; }
public bool Loading { get {return _loading; } set{value = _loading } }
And in your OnBrowserNavigating:
internal async Task OnBrowserNavigating(object sender, WebNavigatingEventArgs e)
{
Loading = true;
if (!(sender is WebView browser))
{
Loading = false;
throw new Exception($"Sender is not of type WebView");
}
if (!Uri.TryCreate(e.Url, UriKind.Absolute, out Uri uri))
{
Loading = false;
throw new Exception($"Uri creation failed for: {e.Url}");
}
if (string.IsNullOrEmpty(_options.EndUrl) == false) //IF THE CONDITION OVER HERE & FOR INNER IF CONDITIONS FAILS, LOADER WASNT SET TO FALSE
{
if (uri.AbsoluteUri.StartsWith(_options.EndUrl))
{
_result = new BrowserResult() { ResultType = BrowserResultType.Success, Response = uri.Fragment.Substring(1) };
e.Cancel = true;
if (!_navPopped)
{
_navPopped = true;
Loading = false;
await PopPageModel();
}
}
}
Loading = false;
}
I am trying to make a really simple app to learn DataBinding and events. The following code is supposed to change the label content when i click on a button, but actually it changes the property but doesn't update the label.
This is the main code :
public MainWindow()
{
InitializeComponent();
environments = new ObservableCollection<Env>();
environments.Add(new Env("env1", new ObservableCollection<Cell>()));
environments.Add(new Env("env2", new ObservableCollection<Cell>()));
foreach (Env e in environments)
{
Label label = new Label
{
Content = e.Name
};
pnlMain.Children.Add(label);
}
}
private void ChangeEnvName_Click(object sender, RoutedEventArgs e)
{
foreach (Env env in environments)
{
env.Name = "test";
}
}
And this is the Env class :
class Env : INotifyPropertyChanged
{
//membres
#region membres
private string _name;
private ObservableCollection<Cell> _cells;
#endregion
//propriétés
#region propriétés
public string Name
{
get { return this._name; }
set
{
if (this._name != value)
{
this._name = value;
this.NotifyPropertyChanged("Name");
}
}
}
public ObservableCollection<Cell> Cells
{
get { return this._cells; }
set
{
if (this._cells != value)
{
this._cells = value;
this.NotifyPropertyChanged("Cells");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
//méthodes
#region méthodes
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
#endregion
//constructeur
#region contructeur
public Env(string name, ObservableCollection<Cell> cells)
{
_name = name;
_cells = cells;
}
#endregion
}
What's the problem? Isn't it suppose the update the label.content when i update Env.Name ?
You haven't bound the Content property of the Label to the Name property. You have just set it to a string. Try this:
foreach (Env e in environments)
{
Label label = new Label();
label.SetBinding(Label.ContentProperty, new Binding("Name") { Source = e });
pnlMain.Children.Add(label);
}
Or create an Environments property that returns environments, set the DataContext to this and bind to Environments[index].Name. If you don specify an explicit Source of the binding, it will look for the property in its current DataContext which may be inherited from a parent element. Please see the docs for more information.
I know this is terribly common issue, but I just can't get the button to update to "Pressed1" and "Pressed2" content when changing "Default" of buttonContent. Having looked at few questions, I can't find the answer that'd work for me, I simply can't find out what is wrong here, so here's the crappy code:
The window with a button
public partial class MainWindow : Window
{
Code_Behind cB;
public MainWindow()
{
cB = new Code_Behind();
this.DataContext = cB;
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
cB.buttonPressed();
}
}
And here's the separate class
public class Code_Behind : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _buttonContent = "Default";
public string buttonContent
{
get { return _buttonContent; }
set {
if (_buttonContent != value)
{
buttonContent = value;
OnPropertyChanged("buttonContent");
}
}
}
public void buttonPressed()
{
int timesPressed = 0;
if (timesPressed != 1)
{
_buttonContent = "Pressed1";
timesPressed++;
}
else if (timesPressed != 2)
{
_buttonContent = "Pressed2";
timesPressed++;
timesPressed = 0;
}
}
protected void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
You are not setting the property, but the backing field. Hence the PropertyChanged event is not fired.
Replace
_buttonContent = "Pressed1";
...
_buttonContent = "Pressed2";
with
buttonContent = "Pressed1";
...
buttonContent = "Pressed2";
Besides that, it is a widely accepted convention to write property names with Pascal casing, i.e. ButtonContent instead of buttonContent.
Moreover, your property setter looks odd (probably because you try to squeeze too much code in one line).
Instead of
set
{
if (_buttonContent != value)
{
_buttonContent = value;
}
OnPropertyChanged("buttonContent");
}
it should certainly be
set
{
if (_buttonContent != value)
{
_buttonContent = value;
OnPropertyChanged("buttonContent");
}
}
This works (the graph is properly loaded):
var oxyPlotView = new OxyPlotView{ Model = GetPlotModelSynch() };
This doesn't (the graph remains empty):
var oxyPlotView = new OxyPlotView();
// Here PlotModel will be loaded asynchronously from the BindingContext:
oxyPlotView.SetBinding(OxyPlotView.ModelProperty, new Binding(nameof(GraphViewModel.PlotModel)));
I have made proper isolated tests to ensure that INotifyPropertyChanged is working properly with my ViewModel. So the problem seems to be that OxyPlotView is built properly only if it has al the info from its inception (?). Is that even possible?
Here is the full ViewModel. INotifyPropertyChanged works because Title is behaving as intended (Title is binded to a Label in the same view).
class GraphViewModel : INotifyPropertyChanged
{
IGraphSeriesGroupRepository _graphSeriesGroupRepository;
private GraphSeriesGroup _graphSeriesGroup;
private ulong _sensorId;
public event PropertyChangedEventHandler PropertyChanged;
private PlotModel _plotModel;
public PlotModel PlotModel
{
get { return _plotModel; }
set
{
if (_plotModel != value)
{
_plotModel = value;
OnPropertyChanged(nameof(PlotModel));
}
}
}
private string _title;
public string Title
{
get { return _title; }
set
{
if (_title != value)
{
_title = value;
OnPropertyChanged(nameof(Title));
}
}
}
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set
{
_isLoading = value;
OnPropertyChanged(nameof(IsLoading));
}
}
public GraphViewModel(IGraphSeriesGroupRepository graphSeriesGroupRepository, ulong sensorId)
{
_graphSeriesGroupRepository = graphSeriesGroupRepository;
_sensorId = sensorId;
Load();
}
public PlotModel GetPlotModelSynch()
{
_graphSeriesGroup = _graphSeriesGroupRepository.GetGraphSeriesGroup(_sensorId);
return GetPlotModel(_graphSeriesGroup);
}
private async void Load()
{
IsLoading = true;
await Task.Delay(5000);
_graphSeriesGroup = await _graphSeriesGroupRepository.GetGraphSeriesGroupAsync(_sensorId);
ApplyChanges();
IsLoading = false;
}
private void ApplyChanges()
{
// ---
Title = _graphSeriesGroup.Title;
PlotModel = GetPlotModel(_graphSeriesGroup);
}
private PlotModel GetPlotModel(GraphSeriesGroup graphSeriesGroup)
{
...
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Update: The only way I've found to make it work is:
private void chatter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(GraphViewModel.PlotModel))
{
_oxyPlotView = new OxyPlotView
{
Model = _graphViewModel.PlotModel
};
_stackLayout.Children.Add(_oxyPlotView);
}
}
...even updating an _oxyPlotView (which was already added to the StackLayout) and calling _oxyPlotView.InvalidateDisplay() didn't work.
Hi,
I'm struggling a bit using the ListBox.DataSource and the INotifyPropertyChanged Interface. I checked several posts about this issue already but I cannot figure out, how to update the view of a ListBox if an element of the bound BindingList is changed.
I basically want to change the color of an IndexItem after the content has been parsed.
Here the relevant calls in my form:
btn_indexAddItem.Click += new EventHandler(btn_indexAddItem_Click);
lst_index.DataSource = Indexer.Items;
lst_index.DisplayMember = "Url";
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
private void btn_indexAddItem_Click(object sender, EventArgs e)
{
Indexer.AddSingleURL(txt_indexAddItem.Text);
}
private void lst_index_DrawItem(object sender, DrawItemEventArgs e)
{
IndexItem item = lst_index.Items[e.Index] as IndexItem;
if (item != null)
{
e.DrawBackground();
SolidBrush brush = new SolidBrush((item.hasContent) ? SystemColors.WindowText : SystemColors.ControlDark);
e.Graphics.DrawString(item.Url, lst_index.Font, brush, 0, e.Index * lst_index.ItemHeight);
e.DrawFocusRectangle();
}
}
Indexer.cs:
class Indexer
{
public BindingList<IndexItem> Items { get; }
private object SyncItems = new object();
public Indexer()
{
Items = new BindingList<IndexItem>();
}
public void AddSingleURL(string url)
{
IndexItem item = new IndexItem(url);
if (!Items.Contains(item))
{
lock (SyncItems)
{
Items.Add(item);
}
new Thread(new ThreadStart(() =>
{
// time consuming parsing
Thread.Sleep(5000);
string content = item.Url;
lock (SyncItems)
{
Items[Items.IndexOf(item)].Content = content;
}
}
)).Start();
}
}
}
IndexItem.cs
class IndexItem : IEquatable<IndexItem>, INotifyPropertyChanged
{
public int Key { get; }
public string Url { get; }
public bool hasContent { get { return (_content != null); } }
private string _content;
public string Content {
get
{
return (hasContent) ? _content : "empty";
}
set
{
_content = value;
ContentChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void ContentChanged()
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public IndexItem(string url)
{
this.Key = url.GetHashCode();
this.Url = url;
}
public override bool Equals(object obj)
{
return Equals(obj as IndexItem);
}
public override int GetHashCode()
{
return Key;
}
public bool Equals(IndexItem other)
{
if (other == null) return false;
return (this.Key.Equals(other.Key)) ||
((hasContent || other.hasContent) && (this._content.Equals(other._content)));
}
public override string ToString()
{
return Url;
}
}
Any ideas what went wrong and how to fix it? I'll appreciate any hint...
It seems to me that the control should redraw when it raises the ListChanged event for that item. This will force it to do so:
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
Indexer.Items.ListChanged += Items_ListChanged;
private void Items_ListChanged(object sender, ListChangedEventArgs e)
{
lst_index.Invalidate(); // Force the control to redraw when any elements change
}
So why doesn't it do that already? Well, it seems that the listbox only calls DrawItem if both DisplayMember changed, and if the INotifyPropertyChanged event was raised from the UI thread. So this also works:
lock (SyncItems)
{
// Hacky way to do an Invoke
Application.OpenForms[0].Invoke((Action)(() =>
{
Items[Items.IndexOf(item)].Url += " "; // Force listbox to call DrawItem by changing the DisplayMember
Items[Items.IndexOf(item)].Content = content;
}));
}
Note that calling PropertyChanged on the Url is not sufficient. The value must actually change. This tells me that the listbox is caching those values. :-(
(Tested with VS2015 REL)