I'm having a bit of a problem with a ListView that is bound to a class that is based on ICollection and where i store my data in a LinkedList.
public class CircularList<T> : ICollection<T>
{
private readonly LinkedList<T> list;
public IEnumerator<T> GetEnumerator()
{
return this.list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
// more code..
}
When I jump to the screen where this is shown, I get the message: A first chance exception of type 'System.InvalidOperationException' occurred in System.dll
Additional information: Collection was modified after the enumerator was instantiated.
However, if I press continue, the list is shown without a problem. The last entry I added is in there. I know it is allready in there before going to that screen, because i also bind to a field in the bottom of my app where i show the last added entry from that Linkedlist. Also, when i go to that screen a second time without adding new entry's into the list no warnings pop up.
Could it be that I have to refresh the IEnumerator before jumping to that screen (or when adding an item to the list), and if so, how? Or could it be something completely else?
I already checked that there are no additions or deletions from the list when the list is being shown on screen.
Related
I have a "long-running" WPF desktop application which uses NLog and the NLogViewer to display runtime status. It is used to monitor a database, pick up requests for processing, and then logging the results. (Yeah -- should have been a service, but we wanted to start out this way first -- baby steps.) The results are logged to a text log file and the NLogViewer shows the current items in the log in real time.
After the app has been running several hours, I've noticed I can only view a portion of the log -- from current entry back to an hour or so earlier. The scrollbar lets me scroll down to the most current entry, but stops way before the first entry. At this time, there isn't a lot of logging going on, i.e., no debugging or tracing, just Info, Warnings, and Errors, so I don't think the size of the log would have an impact.
Is there a way to have the NLogViewer let me view the entire log file or will it only show me a limited window into the log? Is there an alternate NLog viewer WPF control? I'm not really looking at a standalong NLog viewer at this time. If you need more information, please let me know.
Thanks!
Edit: I had some time to revisit this issue and got the NLogViewer source from GitHub. In the code-behind for the view, there is a hard coded value of 50 for the number of entries held in the items source for the ListView. For each item over 50, an item is removed from the beginning of the log. I could, of course, download the source and modify it to my liking which might be the final solution as the project hasn't been updated in 3 years. However, I might try and see if I can create a wrapper around the original control and allow for some basic extensions such as a configurable number of log entries to display and to always show the last entry added. If I have any success, I'll post what I did.
I made a new version of the NLogViewer. Without any limitation. You can also change the style if you want to
https://www.nuget.org/packages/Sentinel.NLogViewer
https://github.com/dojo90/NLogViewer
I spend a lot of time yesterday working on this, trying to just extend the existing class with new behaviors or properties. The biggest issue was with the hard coded 50 entries in a protected method that I couldn't access or override. And since the NLogViewer control is defined with XAML, I couldn't easily subclass it with a "non-XAML" class (compile errors). In the end, it seemed simpler to just use the original source code and make some mods to get the desired results.
I made the following changes:
I added 2 properties to the NLogViewer "view":
[Description("Automatically scroll last entry into view?"), Category("Data")]
public bool ScrollIntoView { get; set; } = true;
[Description("How many log entries should be kept in memory."), Category("Data")]
public int VisibleEntryCount { get; set; } = 50; // Original code default
These will control if the last log entry is scrolled into view and how many log entries will be available for display.
I modified the existing LogReceived handler to use the VisibleEntryCount property.
protected void LogReceived(NLog.Common.AsyncLogEventInfo log)
{
LogEventViewModel vm = new LogEventViewModel(log.LogEvent);
if (this.VisibleEntryCount == 0)
{
this.VisibleEntryCount = 50; // Original code default.
}
this.Dispatcher.BeginInvoke(new Action(() =>
{
if (this.LogEntries.Count >= this.VisibleEntryCount)
{
// We've reached our limit.
this.LogEntries.RemoveAt(0);
}
this.LogEntries.Add(vm);
}));
}
And I added a new event handler for whenever a new log entry is added. This will let me ensure the last entry is always visible (if specified by the user).
private void LogEntries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.ScrollIntoView)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
if (this.logView != null)
{
int count = this.LogEntries.Count;
object selectedItem = (count > 0) ? this.LogEntries[count - 1] : null;
if (selectedItem != null)
{
this.logView.Dispatcher.BeginInvoke((Action)(() =>
{
this.logView.UpdateLayout();
this.logView.ScrollIntoView(selectedItem);
}));
}
}
}
}
}
It seems to be working OK. As always, I'm open to additional solutions or suggestions. Thanks!
first I must apologize because english isn't my mother language, but I'll try to be clear in what I'm asking.
I have a set of rows in a tableview, every row has diferent comboboxs per columns. So, the interaction between combobox must be per row. If in the Combobox A1, I select Item 1, in the Combobox A2 the itemlist will be updated.
My problem is that every combobox A2, B2, C2, etc. Is being updated according the choice in A1... same thing with B1,C1 combobox.
I need to updated just the A2, according to A1. B2 according to B1, etc.
I set the comboboxes by cellfactory, because I have to save the data from behind in a serializable object.
Hope is clear.
Regards.
This is pretty much a pain...
From a TableCell, you can observe the TableRow via it's tableRowProperty().
From the TableRow, you can observe the item in the row, via the table row's itemProperty().
And of course, from the item in the row, you can observe any properties defined in your model class, and update a list of items in the combo box accordingly.
The painful part is that any of these value can, and will at some point change. So the things you need to observe keep changing, and you have to manage adding and removing listeners as this happens.
The Bindings.select method is supposed to help manage things like this, but as of JavaFX 8, it prints huge stack traces to the output as warnings whenever it encounters a null value, which it does frequently. So I recommend doing you own listener management until that is fixed. (For some reason, the JavaFX team doesn't seem to consider this a big deal, even though encountering null values in the path defined in a Bindings.select is explicitly supported in the API docs.)
Just to make it slightly more unpleasant, the getTableRow() method in TableCell<S,T> returns a TableRow, instead of the more obvious TableRow<S>. (There may be a reason for this I can't see, but, well...). So your code is additionally littered with casts.
I created an example that works: apologies for it being based on US geography, but I had much of the example already written. I really hope I'm missing something and that there are easier ways to do this: please feel free to suggest something if anyone has better ideas.
On last note: the EasyBind library may provide a simpler way to bind to the properties along an arbitrary path.
As #James_D's example no longer runs due to link rot, and I was dealing with this same issue, here's how I figured out to create this effect.
View the full test case here.
I extend the builtin ComboBoxTableCell<S, T> to expose necessary fields. The custom TableCell has a Supplier<S> tableValue = (S) this.getTableRow().getItem(); used to access the applicable Data object. Additionally, I reflectively retrieve and store a reference to the cell's ComboBox. Because it is lazily instantiated in the superclass, I also have to set it via reflection before I can get it. Finally, I have to initialize the ComboBox as well, as it would be in javafx.scene.control.cell.CellUtils.createComboBox, since I'm manually creating it. It is important to expose these, as:
In the column's CellFactory, we finish initializing the ComboBoxCell. We just need to create a new instance of our custom ComboBoxTableCell and then when the comboBox is shown for the first time (e.g. we can be sure that we have a Data object associated with the cell), we bind the ComboBox#itemsProperty to a Bindings.When returning the proper ObservableList for the case.
CellFactory:
column1.setCellFactory(c -> {
TransparentComboBoxTableCell<Data, Enum> tcbtc = new TransparentComboBoxTableCell<>();
tcbtc.comboBox.setOnShown(e -> {
if (!tcbtc.comboBox.itemsProperty().isBound()) tcbtc.comboBox.itemsProperty().bind(
Bindings.when(tcbtc.tableValue.get().base.isEqualTo(BASE.EVEN)).then(evens).otherwise(
Bindings.when(tcbtc.tableValue.get().base.isEqualTo(BASE.ODD)).then(odds).otherwise(
FXCollections.emptyObservableList()
))
);
});
return tcbtc;
});
custom ComboBoxTableCell:
public static class TransparentComboBoxTableCell<S, T> extends ComboBoxTableCell<S, T> {
public TransparentComboBoxTableCell() {
this(FXCollections.observableArrayList());
}
public TransparentComboBoxTableCell(ObservableList<T> startingItems) {
super(startingItems);
try {
Field f = ComboBoxTableCell.class.getDeclaredField("comboBox");
f.setAccessible(true);
f.set(this, new ComboBox<>());
comboBox = (ComboBox<T>) f.get(this);
// Setup out of javafx.scene.control.cell.CellUtils.createComboBox
// comboBox.converterProperty().bind(converter);
comboBox.setMaxWidth(Double.MAX_VALUE);
comboBox.getSelectionModel().selectedItemProperty().addListener((ov, oldValue, newValue) -> {
if (this.isEditing()) {
this.commitEdit((T) newValue);
}
});
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException ex) {
Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex);
throw new Error("Error extracting 'comboBox' from ComboBoxTableCell", ex);
}
tableValue = () -> (S) this.getTableRow().getItem();
}
public final ComboBox<T> comboBox;
public final Supplier<S> tableValue;
}
I am using IDataErrorInfo inherited business objects for validation.
public string UserId { get; set; }
public string this[string columnName]
{
get
{
string result = null;
if (columnName == "UserId")
{
if (string.IsNullOrEmpty(UserId))
result = "Please enter User Id";
}
}
}
I want to clear all the validation errors when I click a button on the menu - like - LogOut.
Window is making the Login panel visible but the previous panel's validation error marks are still appearing in the current login panel.
I tried all the options to assign NULL datacontext, fresh entity object...but no luck
I appreciate your help.
Using the IDataErrorInfo interface is an error-first type approach. This means that you will see the error(s) until they are cleared. You can see that there is no setter on the indexer.
The original IDataErrorInfo interface is not overly useful by itself as it only deals with one error at a time. I added the following field into my BaseDataType class:
protected ObservableCollection<string> errors = new ObservableCollection<string>();
In my actual data classes, I have the following property:
public override ObservableCollection<string> Errors
{
get
{
errors = new ObservableCollection<string>();
errors.AddUniqueIfNotEmpty(this["Property1"]);
errors.AddUniqueIfNotEmpty(this["Property2"]);
errors.AddUniqueIfNotEmpty(this["PropertyN"]);
return errors;
}
}
The AddUniqueIfNotEmpty method is an extension method which I believe is self-explanatory. This property calls the indexer any number of times and compiles all of the results into a ObservableCollection<string> collection ready to be displayed in the UI. You'll need to call the INotifyPropertyChanged.PropertyChanged event with the name Errors when Property1, Property2 and PropertyN are updated to make this work.
You could do something like this, but add a setter for you to pass in an empty collection or string when you want to clear the errors.
I use the MVVM Light Toolkit to define the association between the view-model and the view.
The container is instructed to register a view-model as a singleton instance. Thus, the same instance will always be returned when the GagaViewModel is required:
public GagaViewModel GagaViewModel
{
get
{
var vm = ServiceLocator.Current.GetInstance<GagaViewModel>();
vm.Setup(); //Clear the ObservableCollection
return vm;
}
}
You can click on a thumbnail item on PriorGaga.xml. The self-chosen item is then selected in the GridView "MyGridView" in Gaga.xaml. Code-behind file of Gaga.xaml:
protected override async void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
var itemId = navigationParameter as String;
if (String.IsNullOrEmpty(itemId))
{
throw new ArgumentException("navigationParameter was either null or empty");
}
await ((GagaViewModel)DataContext).Init(itemId); //Busy(-Indicator) while loading data from server, filling the ObservableCollection and writing the selected item down
BringItemIntoView();
}
private void BringItemIntoView()
{
var vm = (GagaViewModel)DataContext;
Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => MyGridView.ScrollIntoView(vm.SelectedItem));
}
That works fine. As a sample: Item #45 appears within the viewport immediately (correct viewport position from the beginning).
But when you click the back button and return to Gaga.xaml by selecting an arbitrarily thumbnail item (let's just say #29), you will see item #1 and then the switch to #29 (the viewport is moving over the container). Do someone know what's going on under there? Are there any virtualized items in the container from the preceding Gaga.xaml visit?
My understanding is that the lifespan of the instance of your Gaga page is determined by its NavigationCacheMode property. By default, it is set to Disabled. Assuming that you haven't changed this property, you should be seeing a new instance of your Gaga page every time you navigate to it. You can verify this behavior by setting a breakpoint in its constructor. Consequently, I would think that each time you navigate to Gaga, the behavior of the UI should be identical, because everything is fresh.
(I wanted to add this as a comment, since I haven't actually answered your question, but sadly I do not have enough rep. I apologize in advance; please do not smite me down!)
I have a MVVM pattern test application that is tossing a lot of
System.Windows.Data Error: 17 : Cannot get 'Item[]' value (type 'ChuteGroup') from 'Groups' (type 'ChuteGroupsModel'). BindingExpression:Path=Groups[0]; DataItem='MainViewModel' (HashCode=41802290); target element is 'ChuteView' (Name=''); target property is 'DataContext' (type 'Object') ArgumentOutOfRangeException:'System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values. Parameter name: index'
This happens when I enter my "onRefresh" routine in the viewmodel. I have an observable collection called "Current" and the first thing I do in the refresh routine is clear the entries from the Current collection. I then get a slew of these data error 17 messages because I think in the background the bindings are trying to update and now there isn't anything in the collection until I re-fill and re-create each entry into the observable collection.
Is there a better way of doing this? Runtime performance doesn't seem to be affected by this but I don't like errors in my output window. I found that if I didn't clear the collection it just doubled in size each time the viewmodel refreshed itself. Since this collection is used in conjunction with 54 UI elements, which are binded by index, the collection can't double in size or everything wont point to the correct UI element.
private void FetchData()
{
ChuteGroupsModel.isDuringRefresh = true;
DataSet sqldata = new DataSet();
SqlConnection conn = new SqlConnection("Data Source=(local);Initial Catalog=ScratchPaper;User ID=somecacct;Password=somepassword;Connect Timeout=5");
SqlCommand cmd = new SqlCommand("Select chuteGroup, chuteGroupDef, TotalChutes, UpPackWave, UpColorId, UpPackWaveTS, DownPackWave, DownColorId, DownPackWaveTS from ChuteGroups Order by chuteGroup asc",conn);
SqlDataAdapter da = new SqlDataAdapter(cmd);
try
{
da.Fill(sqldata);
}
catch (Exception Ex){ MessageBox.Show(Ex.ToString());}
//DataSet sqldata = this.DataLayer.getDataSet("Select * from AvailableColors Order by ID asc", CommandType.Text, null);
foreach (DataRow row in sqldata.Tables[0].Rows)
{
ChuteGroup group = new ChuteGroup((int)row.ItemArray[0], (string)row.ItemArray[1], (int)row.ItemArray[2], (string)row.ItemArray[3],(string)row.ItemArray[4], (DateTime)row.ItemArray[5], (string)row.ItemArray[6], (string)row.ItemArray[7], (DateTime)row.ItemArray[8]);
Add(group);
}
ChuteGroupsModel.isDuringRefresh = false;
}
private void onRefresh(object sender, System.EventArgs e)
{
try
{
if (ChuteGroupsModel.isDuringRefresh)
{
return;
}
Refresh();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message.ToString());
}
}
public void Refresh()
{
Current.Clear(); //Current is a static reference to my collection
FetchData();
}
You are right in assuming that the binding errors are due to your collection being cleared. As you aren't experiencing any performance penalties, I wouldn't worry about it too much.
If you are really annoyed by them, you can create a new observable collection type to allow you to set the new values. If you crack open the ObservableCollection class in Reflector, you'd copy the implementation, and add one new method like so:
public class ObservableList<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
// ObservableCollection implementation here
...
public void SetItems(IEnumerable<T> items)
{
this.CheckReentrancy();
base.ClearItems();
int i = 0;
foreach (var item in items)
{
base.InsertItem(i, item);
++i;
}
this.OnPropertyChanged("Count");
this.OnPropertyChanged("Item[]");
this.OnCollectionReset();
}
}
Then, instead of clearing your data and adding it one row at a time, you'd call the SetItems method.
I know it's been a while since this question has been asked, but one method that has solved this type of issue for me is using RemoveAt(0) (and/or whatever locations you need cleared) and subsequently Adding your data to the collection. There seems to be some sort of timing issue with Clear() in that for a few brief instants, calls from the bindings for the data returns an empty collection, thus the output messages. This may seem a bit detailed and perhaps brutish in comparison with a more generic clear, but it does remove the errors and noticeably improves performance.