Obtaining visibility status of list view delegate qml - qml

I am working on chat system, i have a conversation page with a list view inside it, the list view delegates are text conversations. I want to add seen/unseen or read/unread to conversation delegates, the list view sourced by a custom model and i need something like delegates attached property which changes when the delegate is actually being viewed. is there any attached property which tell me the visibility status of each delegate items?

You have two problems to solve:
How to store the information that the message has been viewd
When to set it as read.
So one after another...
The custom property within the delegate is no good idea, as the information stored in it will be lost once the delegate will leave the viewport of the ListView and is destroyed.
The best solution would be, to store it in the ListView's model. This way it is also possible, if the model is somewhat persistend, that the information survives a restart of the app.
Maybe you might achive something with the next options I will present, in combination with Settings but I would consider this hackish.
If that is not possible, and the information does not need to survive an app's restart, you need to think about a way of storing it outside the ListView. For example you could have a array or object/dictionary to store it (beware: No change notifications). You might also use a second ListView that you keep in sync (which might prove not so easy!).
Finally you might put your ListView as a model in an Instantiator, which instantiates simple QtObjects with one property: read.
The latter would be the easiest way to store it outside the delegates and the model, so I will show you how it works:
Instantiator {
id: additionalInfo
model: customModel
delegate: QtObject {
property bool read // <--- This is a custom defined property
}
}
ListView {
model: customModel
delegate: MyDelegate {
onRead: additionalInfo.objectAt(index).read = true
Text {
text: additionalInfo.objectAt(index).read ? 'read' : 'unread'
}
}
}
And now for the second part: When is it acutally read
The problem with the visibility you already discovered. Items become visible even outside the listView when they are created for the buffer. So you can't use this information. But you have the following information:
The position of the listModel.contentItem (x and y)
The position of the delegate in relation to the listModel.contentItem
The width and height of your delegate
So you can say: The delegate is fully visible iff:
listModel.contentItem.x + delegate.x >= 0
listModel.contentItem.y + delegate.y >= 0
listModel.contentItem.y + delegate.y + delegate.height <= listModel.height
listModel.contentItem.x + delegate.x + delegate.width <= listModel.width
An example, on how a Item might check if it is visible in a Flickable (remember: a ListView IS a Flickable) can be found here
This is: If it is possible to have the whole delegate with the view. If it is larger - well you need to define some criterias when the delegate is considered read.
But again: If there is any posibility to have it as a role in your model, put it there!

Related

DDD - Invariant enforcement using instance methods and a factory method

I'm designing a system using Domain-Driven design principals.
I have an aggregate named Album.
It contains a collection of Tracks.
Album instances are created using a factory method named create(props).
Rule 1: An Album must contain at least one Track.
This rule must be checked upon creation (in Album.create(props)).
Also, there must a method named addTrack(track: Track) so that a new Track can be added after the instance is created. That means addTrack(track: Track) must check the rule too.
How can I avoid this logic code duplication?
Well, if Album makes sure it has at least one Track upon instantiation I don't see why addTrack would be concerned that rule could ever be violated? Did you perhaps mean removeTrack?
In that case you could go for something as simple as the following:
class Album {
constructor(tracks) {
this._tracks = [];
this._assertWillHaveOneTrack(tracks.length);
//add tracks
}
removeTrack(trackId) {
this._assertWillHaveOneTrack(-1);
//remove track
}
_assertWillHaveOneTrack(change) {
if (this._tracks.length + change <= 0) throw new Error('Album must have a minimum of one track.');
}
}
Please note that you could also have mutated the state first and checked the rule after which makes things simpler at first glance, but it's usually a bad practice because the model could be left in an invalid state if the exception is handled, unless the model reverts the change, but that gets even more complex.
Also note that if Track is an entity, it's probably a better idea not to let the client code create the Track to preserve encapsulation, but rather pass a TrackInfo value object or something similar.

Chart data of object gets overwritten, but only the first object that is interacted with is affected

Here is this a code sand box proving and showcasing this issue: https://codesandbox.io/embed/ql4rm9734w?fontsize=14
When a user clicks on a button in the app's. A widget is meant to show the data of that object. The the object contains an array that is used to produce a graph. The first object's button click seems to display and function correctly. So does the second and the third. But when the first objects button is clicked again the chart data property of the object is overwritten with the chart data of the previously clicked object.
The application has been built in Vue.Js, with Highcharts, and Highcharts official Vue wrapper rendering the charts. The data is stored in a Vuex store.
The page gets populated with a button for each object. When a objects button is clicked a custom event is fired containing the object data. The object click event handler mutates the store passing the object to the store to be saved as a active marker object. The object Widget that displays the data to the user gets its data from the stores active marker object.
this process works fine for every other object that uses this system. It also only ever effects the first object clicked, all subsequent objects are unaffected and work correctly.
I have tried the following with no luck
Vue dev tools and debugging, shows the symptoms of the error but does not point to where the error takes place.
I have tried making the data property a pseudo private property that can only be accessed with setters and getters. The setter is never called.
Added second property in the class to act as a not modified storage variable for the original data given at construction time. This second property also gets modified.
When examining the store in depth, it looked like the object array in the store was not affected by the bug. However when refactored to use the object from the store directly the bug is still there.
I tried to separate out the data into a separate state property that is not related to the object in any direct way... still the same bug.
I also tried with a every small data array (15 elements) still the bug persisted.
I have even built a mini replica project in the hopes that at the smallest scale the bug does not appear and hopefully it would be a silly typo or something... but again, even this mini version of my app still shows the bug. the Mini version can be found here: https://github.com/ChadRoberts21/ChartMapBug
Built a more refined smaller example: https://codesandbox.io/embed/ql4rm9734w?fontsize=14
The code is available from https://codesandbox.io/embed/ql4rm9734w?fontsize=14
I expect that the correct chart data is always shown in the object widget and for the object to not have its data property overridden at all unless I expressly choose to do so in a Vuex mutation.
The problem occurs, because of fact that the Highcharts mutates the data, which not exactly complies the Vue conceptions. Generally, if you are able to avoid the data mutation you shouldn't do that at all. I've just answered that question directly on the highcharts-vue repository, so there you can find more specific description about why the issue occurs.
In essence (for another users searching for the answer on that question), the best way out of the problem will be to apply a spread operator when assigning a new data to series:
FooWidget.vue (chartOptions() computed property part)
series: [{
showInLegend: false,
type: "column",
color: this.foo.colour,
data: [...this.foo.data],
pointInterval: this.foo.interval,
pointStart: this.foo.startPoint,
gapSize: 4,
tooltip: {
valueDecimals: 2
},
fillColor: {
linearGradient: {
x1: 0,
y1: 0,
x2: 0,
y2: 1
}
},
threshold: null
}]
Live example: https://codesandbox.io/s/w2wyx88vxl
Best regards!
I think the problem is you are using computed for fetching data. when you pass data in click event it's actually updated foos array of the store(reference of state.foos). so when you click on test2 the data in the store of foos array is updated.
Try to console log foos array in store setActiveFoo method you can see that foos array is updated with other values.
setActiveFoo(state, payload) {
console.log("In store foos")
console.log(state.foos);
state.activeFoo = payload;
}
Try this code. I think you need to send the copy of foos array in computed it will solve the problem.
computed: {
foos() {
return JSON.parse(JSON.stringify(this.$store.getters.foos));
},
activeFoo() {
return JSON.parse(JSON.stringify(this.$store.getters.activeFoo));
}
}

NStreeController - NSoutlineView get cell binding object

I have the following scenario in a NSoutlineView:
ParentObject [checkbox]
- ChildObject 1 [checkbox]
- ChildObject 2 [checkbox]
Each checkbox has a binding set up to a bool value of the respective object in a NSTreeController. When a user selects the parentObject checkbox, the respective children checkboxes should also be set. However, when a child object checkbox is set, the parent checkbox should not be affected. I cannot seem to get the parent functionality working properly.
My current attempted solution to the problem is:
when the checkbox is set call:
-(IBAction)CheckSelected:(NSButtonCell *)sender
{
// Somehow access the cells bound object in the NSTreeController ?????
}
However from my research I have not been able to find a way to get access to the cell's respective object in the NSTreeController.
Any insight on the problem would be greatly appreciated. I feel like this is a common problem that people would run into using an NStreeController and I am curious if I am taking the proper approach.
Thanks :)
The checkboxes shouldn’t be set up to call an action—they should be bound to a property, like, say, “isChecked”.
In your ParentObject you’ll have code similar to:
- (void)setIsChecked:(BOOL)isChecked;
{
_isChecked = isChecked;
for (ChildObject *childObject in self.children)
childObject.isChecked = isChecked;
}
Since the children’s checkboxes are also bound, the children’s new state will be reflected in the outline view immediately.

Interactive QSqlTableModel

Please could you give me an advice. I am using QSqlTableModel class to access the database table and QTableView to view it. What signal of what instance should I handle to know about user move the cursor in QTableView?
I want to update the content of TableView B after the cursor moved in QTableView A (Table B have foreign keys to table A in database)
May be somewhat from this http://doc.trolltech.com/latest/qabstractitemmodel.html?
Thanks.
Ivan, if you are talking about table cursor, you can reimplement QAbstractItemView::moveCursor method which is virtual.
If you're talking about mouse cursor, you can use QAbstractItemView::viewportEvent method to detect mouse move event. You need to set QWidget::setMouseTracking(true) to the viewport of your QTableView.
Hope that helps
Another way is using the selection model
Using a selection model
The standard
view classes provide default selection
models that can be used in most
applications. A selection model
belonging to one view can be obtained
using the view's selectionModel()
function, and shared between many
views with setSelectionModel(), so the
construction of new selection models
is generally not required.
If you have an shared selection model the views will be updated not matter which one changes. You can then react to it. The selection flags control if you want a cell, row or multiple selections.
See also working with selections :
//selection changes shall trigger a slot
QItemSelectionModel *selectionModel= treeView->selectionModel();
connect(selectionModel, SIGNAL(selectionChanged (const QItemSelection &, const QItemSelection &)),
this, SLOT(selectionChangedSlot(const QItemSelection &, const QItemSelection &)));
}

Databinding Silverlight Comboboxes with Lists of Objects - working but ugly

I'm developing a business application, using Silverlight for the UI and a WCF webservice for the back-end. In the database I have a number of lookup tables. When the WCF service returns a business object, one of the properties contains the entire row out of the lookup table instead of just the foreign key, so in the UI I can display things like the description from the lookup table without making another call to the service. What I am trying to do at the moment is provide a combobox bound to the entire list of lookup values and have it update properly. The business object I'm dealing with in this example is called Session and the lookup is called SessionType.
Below is the definition of the combobox. The DataContext is set to an instance of Session. I am setting an ItemTemplate because the combobox is displaying more than just a list of strings.
<ComboBox
x:Name="SessionTypesComboBox"
ItemTemplate="{StaticResource SessionTypeDataTemplate}"
ItemsSource="{Binding Source={StaticResource AllSessionTypes}}"
SelectedItem="{Binding Path=SessionType, Mode=TwoWay}"
/>
Both the business object and the lookup table are being loaded asynchronously via the web service. If I do nothing else, the combobox list will be populated with SessionTypes, but it will not show the initial SessionType value from Session. However Session will be updated with the correct SessionType if the combobox selection is changed.
What seems to be happening is that the SelectedItem binding cant match the SessionType in Session to its equivalent in the SessionType list. The object values are the same but the references are not.
The workaround I have found is to load the Session and the SessionTypes list, then update the current SessionType of Session with the corresponding one from the SesstionTypes list. If I do that then the combobox displays correctly. However to me this has a bad code smell. Because everything is loaded asyncronously, I have to determine when everything is available. Here's how I'm doing that:
In the code-behind of my Silverlight user control:
// incremented every time we get data back during initial form load.
private volatile int m_LoadSequence = 0;
...
// Loaded event, called when the form is er... loaded.
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// load session types
var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
if (sessionTypes != null)
{
sessionTypes.DataLoadCompleted += (s, ea) =>
{
IncrementLoadSequence();
};
sessionTypes.LoadAsync();
}
// start loading another lookup table, same as above
// omitted for clarity
// set our DataContect to our business object (passed in when form was created)
this.LayoutRoot.DataContext = this.m_Session;
IncrementLoadSequence();
}
// This is the smelly part. This gets called by OnBlahCompleted events as web service calls return.
private void IncrementLoadSequence()
{
// check to see if we're expecting any more service calls to complete.
if (++m_LoadSequence < 3)
return;
// set lookup values on m_Session to the correct one in SessionType list.
// Get SessionType list from page resources
var sessionTypes = this.Resources["AllSessionTypes"] as Lookups.AllSessionTypes;
// Find the matching SessionType based on ID
this.m_Session.SessionType = sessionTypes.Where((st) => { return st.SessionTypeID == this.m_Session.SessionType.SessionTypeID; }).First();
// (other lookup table omitted for clarity)
}
So basically I have a counter that gets incremented each time I get data back from the webservice. Since I'm expecting 3 things (core business object + 2 lookup tables), when that counter gets to 3 I match up the references.
To me, this seems very hacky. I would rather see the combobox specify a ValueMemberPath and SelectedValue to match the selected item with one in the list.
Can anyone see a cleaner way of doing this? This situation is very common in business apps, so I'm sure there must be a nice way of doing it.
Geoff,
To confirm I understand your problem: the databinding infrastructure doesn't seem to recognise that two objects you consider 'equal' are actually equal - therefore the initial SelectedItem isn't set correctly since the databinding doesn't find a reference-equals object in your StaticResource collection to match Session.SessionType.
You get around this by 'flattening' the references (ie. you force the Session.SessionType to be reference-equals in the Where((st)...First() code.
We have had a similar problem.
It does kinda of make sense that Silverlight won't automatically 'equate' two objects from difference 'sources' just because you know they represent the same data. Like you said "The object values are the same but the references are not". But how can you MAKE the databinding equate them?
Things we thought of/tried:
implementing .Equals() on the class (SessionType in your case)
implementing operator == on the class (SessionType in your case)
implementing IEquatable on the class (SessionType in your case)
making the collection only Strings and binding to a string property
but in the end we have given up and used the same approach as you - 'extracting' the correct reference-equals object from the 'collection' (after everything is loaded) and poking it into the SelectedItem-bound property.
I agree with you about the code-smell, and suspect there must be a better solution. So far all our debugging in the property accessors and no-op IValueConverters hasn't found a solution -- but if we do I'll post it back here too.
I'm not sure I'm fully understanding the problem (it's early :)) But can't you just transfer all the items you need in one call? (even if you have to wrap the 3 in a new DTO class), then you can just update the current session type using a complete event. It's still not perfect, but at least you don't have to keep any counters.
I'd also move all that logic to a ViewModel and just bind to that, but that's just me :)
You'd be better off binding to an ObservableCollection then using some other code (a View Model part of a MVVM isn't a bad choice) to update it in the background. That way you get separation from the UI and its a lot easier to handle the updates as the UI is just bound.
Thanks for the answers, all of the above were useful! I'm moving towards the MVVM way as well as combining several service calls into a single one (also reduces round-trip overhead). Looks like I'll stick with the lookup re-referencing for the time being - if I find a better way I'll post it as well.
geofftnz,
Have you find any nice solution for this?
CraigD,
I doubt that overriding Equals etc is a good solution. First, this is to be done inside the generated proxy class SessionType, so these changes will be lost on each service reference update. Second, notification in the SessionType setter (here SessionType is the same generated client proxy class) uses ReferenceEquals call... so that's one more place to touch the generated code! OK, the first thing can be done via handmade partial class SessionType (and so will not be lost after updates), but the second thing certainly cannot be done the same way.