In my current project I need to work with an older version of C++ Builder (5).
I tried to implement drag and drop on a TListView with
MultiSelect
DragMode dmManual
BeginDrag()
The dragging code is very simple:
void __fastcall TFormMain::ListView1MouseMove( TObject *Sender, TShiftState Shift, int X, int Y)
{
if ( Shift.Contains( ssLeft ) )
{
// if ( !dynamic_cast< TControl * >( Sender )->Dragging() )
if ( !static_cast< TControl * >( Sender )->Dragging() )
{
ListView1->BeginDrag( false );
}
}
}
Everything works pretty fine as long as I compile and run this code on newer versions of C++ Builder (e.g. XE2 or XE10). But when I compile this code on C++ Builder 5 or C++ Builder 6 the ListView control shows an ugly behaviour: As soon as I start dragging one of the Items in the ListView a very heavy flickering appears, which makes the mouse cursor almost immoveable.
Now my Question: Is it possible to implement a flicker-free dragging on these old versions of C++ Builder which uses dmManual with MultiSelect?
Additional stuff:
The ListView is populated this way:
void __fastcall TFormMain::ButtonPopulateListViewClick(TObject *Sender)
{
for ( int i = 0; i < 20; ++i )
{
AnsiString Name = System::Sysutils::Format( "Record Nr. %d", ARRAYOFCONST(( i )) );
TListItem *ListItem = ListView1->Items->Add();
ListItem->Data = new Record( Name, i );
ListItem->Caption = Name;
}
}
...and this is the Record class:
class Record
{
private:
AnsiString Name;
int Value;
public:
Record( AnsiString AName, int AValue ) : Name( AName ), Value( AValue ) {}
AnsiString GetName( void ) { return Name; };
};
Edit:
I know that flickering disappears when I switch to dmAutomatic and use the OnStartDrag() event of the TListView. But due to other reasons that is not an option and therefore not the answer to my question.
Thanks!
Related
Beginner-level questions. I’m creating a counter application (first application from The 7 Tasks). I created this application in one file and it is working fine. Following is the code.
class Application : Gtk.Application {
public int val = 0;
public Application() {
Object(
application_id: "com.github.uname.counter",
flags: ApplicationFlags.FLAGS_NONE
);
}
protected override void activate() {
var window = new Gtk.ApplicationWindow(this);
window.default_height = 30;
window.default_width = 300;
window.title = "Counter";
var grid = new Gtk.Grid();
grid.column_homogeneous = true;
grid.row_homogeneous = true;
grid.row_spacing = 5;
grid.column_spacing = 5;
var entry = new Gtk.Entry();
entry.text = val.to_string();
entry.editable = false;
grid.attach(entry, 0, 0, 1, 1);
var button1 = new Gtk.Button.with_label("Counter");
grid.attach(button1, 1, 0, 1, 1);
button1.clicked.connect (() => {
this.val = this.val + 1;
entry.text = this.val.to_string();
});
window.add(grid);
window.show_all();
}
public static int main(string[] args) {
var application = new Application();
return application.run(args);
}
}
Now, I'm trying to divide the above code into separate files such as Application.vala, Entry.vala, and Button.vala. Here is the code for these files.
Code for Application.vala.
class Application : Gtk.Application {
public int val = 0;
public Application() {
Object(
application_id: "com.github.chauhankiran.counter",
flags: ApplicationFlags.FLAGS_NONE
);
}
protected override void activate() {
var window = new Gtk.ApplicationWindow(this);
window.default_height = 30;
window.default_width = 300;
window.title = "Counter";
var grid = new Gtk.Grid();
grid.column_homogeneous = true;
grid.row_homogeneous = true;
grid.row_spacing = 5;
grid.column_spacing = 5;
var entry = new Entry(val);
grid.attach(entry, 0, 0, 1, 1);
var button1 = new Button(val);
grid.attach(button1, 1, 0, 1, 1);
window.add(grid);
window.show_all();
}
public static int main(string[] args) {
var application = new Application();
return application.run(args);
}
}
Code for Entry.vala.
public class Entry : Gtk.Entry {
public Entry(int val) {
text = val.to_string();
}
construct {
editable = false;
}
}
Code for Button.vala.
public class Button : Gtk.Button {
// Is it correct?
public int val;
public Button(int val) {
this.val = val;
}
construct {
label = "Counter";
}
// How to write this within Button.vala from Application.vala?
// How to get entry widget in this class?
button1.clicked.connect (() => {
this.val = this.val + 1;
entry.text = this.val.to_string();
});
}
Now, I have the following questions.
Entry.vala accepts val as initial value. I don't know how to pass it in construct. So, I used public object method. Is it correct way?
In Button.vala I need val as well access to entry so that I can get access to entry in Button.vala? Or this is incorrect way to do the code? If that is that is the case, please suggest correct way. Currently separate files code throws error as I don’t know how to connect and pass the information correctly.
The 7 Tasks are a good exercise to learn, and you seem to be off to a great start!
Entry.vala accepts val as initial value. I don't know how to pass it in construct. So, I used public object method. Is it correct way?
The preferred way to handle construction in Vala is using GObject-style construction: https://wiki.gnome.org/Projects/Vala/Tutorial#GObject-Style_Construction
What you're doing would technically work, but using the GObject-style you'd end up with something like the following:
public class Entry : Gtk.Entry {
public Entry(int val) {
Object (
text: val.to_string(),
editable: false
);
}
}
The important things to note here are:
This only works for properties declared as construct or set
The syntax is slightly different than what you were doing (property: value vs. member = value)
And one other little optimization:
editable is also a property that can be set in the constructor, so no need for a construct block here!
Notice that you can make some similar changes to your Button class as well!
In Button.vala I need val as well access to entry so that I can get
access to entry in Button.vala? Or this is incorrect way to do the
code? If that is that is the case, please suggest correct way.
Currently separate files code throws error as I don’t know how to
connect and pass the information correctly.
Currently your Button code has references to the Entry in it. I would advise against this from an object-oriented programming (OOP) perspective (you may hear people toss around terms like Single-Responsibility or Separation of Concerns, etc.), but the gist is that the button should just focus on being what it is: a button. A button doesn't need to be aware of the presence of the entry, and the entry doesn't need to exist in order to have a button. Your logic of handling what happens between widgets when the button is clicked should happen at a level above. In this case, that would be in your Application class where you've created both of those widgets:
...
var entry = new Entry(val);
grid.attach(entry, 0, 0, 1, 1);
var button1 = new Button(val);
grid.attach(button1, 1, 0, 1, 1);
button1.clicked.connect (() => {
// Update the entry
});
...
Let's take a quick look at your button:
public class Button : Gtk.Button {
// Is it correct?
public int val;
...
It's not wrong, and there are many ways to do what you're doing. So let's roll with it as is.
At this point you've got your button which updates an internal int value every time it's clicked and you now need to update the entry to display the new value. Currently you have:
button1.clicked.connect (() => {
this.val = this.val + 1;
entry.text = this.val.to_string();
});
Since this is now being handled in the Application class, you'll need to change those references to this, since you want to reference and update val from the button, not the variable in Application that you're using for the initial value:
button1.clicked.connect (() => {
button1.val = button1.val + 1;
entry.text = button1.val.to_string();
});
Hopefully that helped a bit, you were 99% of the way there with splitting it into multiple classes! Keep it up, and good luck on the next tasks!
So when writing UI in GTK it's generally preferrable to handle reading of files, etc. in an Async Method. things such as listboxes, are generally bound to a ListModel, the items in the ListBox updated in accordance with the items_changed signal.
So if I have some class, that implements ListModel, and has an add function, and some FileReader that holds a reference to said ListModel, and call add from an async function, how do i make that in essence triggering the items_changed and having GTK update accordingly?
I've tried list.items_changed.connect(message("Items changed!")); but it never triggers.
I saw this: How can one update GTK+ UI in Vala from a long operation without blocking the UI
but in this example, it's just the button label that is changed, no signal is actually triggered.
EDIT: (Codesample added at the request of #Michael Gratton
//Disclaimer: everything here is still very much a work in progress, and will, as soon as I'm confident that what I have is not total crap, be released under some GPL or other open license.
//Note: for the sake of readability, I adopted the C# naming convention for interfaces, namely, putting a capital 'I' in front of them, a decision i do not feel quite as confident in as I did earlier.
//Note: the calls to message(..) was put in here to help debugging
public class AsyncFileContext : Object{
private int64 offset;
private bool start_read;
private bool read_to_end;
private Factories.IVCardFactory factory;
private File file;
private FileMonitor monitor;
private Gee.Set<IVCard> vcard_buffer;
private IObservableSet<IVCard> _vCards;
public IObservableSet<IVCard> vCards {
owned get{
return this._vCards;
}
}
construct{
//We want to start fileops at the beginning of the file
this.offset = (int64)0;
this.start_read = true;
this.read_to_end = false;
this.vcard_buffer = new Gee.HashSet<IVCard>();
this.factory = new Factories.GenericVCardFactory();
}
public void add_vcard(IVCard card){
//TODO: implement
}
public AsyncFileContext(IObservableSet<IVCard> vcards, string path){
this._vCards = vcards;
this._vCards = IObservableSet.wrap_set<IVCard>(new Gee.HashSet<IVCard>());
this.file = File.new_for_path(path);
this.monitor = file.monitor_file(FileMonitorFlags.NONE, null);
message("1");
//TODO: add connect
this.monitor.changed.connect((file, otherfile, event) => {
if(event != FileMonitorEvent.DELETED){
bool changes_done = event == FileMonitorEvent.CHANGES_DONE_HINT;
Idle.add(() => {
read_file_async.begin(changes_done);
return false;
});
}
});
message("2");
//We don't know that changes are done yet
//TODO: Consider carefully how you want this to work when it is NOT called from an event
Idle.add(() => {
read_file_async.begin(false);
return false;
});
}
//Changes done should only be true if the FileMonitorEvent that triggers the call was CHANGES_DONE_HINT
private async void read_file_async(bool changes_done) throws IOError{
if(!this.start_read){
return;
}
this.start_read = false;
var dis = new DataInputStream(yield file.read_async());
message("3");
//If we've been reading this file, and there's then a change, we assume we need to continue where we let off
//TODO: assert that the offset isn't at the very end of the file, if so reset to 0 so we can reread the file
if(offset > 0){
dis.seek(offset, SeekType.SET);
}
string line;
int vcards_added = 0;
while((line = yield dis.read_line_async()) != null){
message("position: %s".printf(dis.tell().to_string()));
this.offset = dis.tell();
message("4");
message(line);
//if the line is empty, we want to jump to next line, and ignore the input here entirely
if(line.chomp().chug() == ""){
continue;
}
this.factory.add_line(line);
if(factory.vcard_ready){
message("creating...");
this.vcard_buffer.add(factory.create());
vcards_added++;
//If we've read-in and created an entire vcard, it's time to yield
message("Yielding...");
Idle.add(() => {
_vCards.add_all(vcard_buffer);
vcard_buffer.remove_all(_vCards);
return false;
});
Idle.add(read_file_async.callback);
yield;
message("Resuming");
}
}
//IF we expect there will be no more writing, or if we expect that we read ALL the vcards, and did not add any, it's time to go back and read through the whole thing again.
if(changes_done){ //|| vcards_added == 0){
this.offset = 0;
}
this.start_read = true;
}
}
//The main idea in this class is to just bind the IObservableCollection's item_added, item_removed and cleared signals to the items_changed of the ListModel. IObservableCollection is a class I have implemented that merely wraps Gee.Collection, it is unittested, and works as intended
public class VCardListModel : ListModel, Object{
private Gee.List<IVCard> vcard_list;
private IObservableCollection<IVCard> vcard_collection;
public VCardListModel(IObservableCollection<IVCard> vcard_collection){
this.vcard_collection = vcard_collection;
this.vcard_list = new Gee.ArrayList<IVCard>.wrap(vcard_collection.to_array());
this.vcard_collection.item_added.connect((vcard) => {
vcard_list.add(vcard);
int pos = vcard_list.index_of(vcard);
items_changed(pos, 0, 1);
});
this.vcard_collection.item_removed.connect((vcard) => {
int pos = vcard_list.index_of(vcard);
vcard_list.remove(vcard);
items_changed(pos, 1, 0);
});
this.vcard_collection.cleared.connect(() => {
items_changed(0, vcard_list.size, 0);
vcard_list.clear();
});
}
public Object? get_item(uint position){
if((vcard_list.size - 1) < position){
return null;
}
return this.vcard_list.get((int)position);
}
public Type get_item_type(){
return Type.from_name("VikingvCardIVCard");
}
public uint get_n_items(){
return (uint)this.vcard_list.size;
}
public Object? get_object(uint position){
return this.get_item((int)position);
}
}
//The IObservableCollection parsed to this classes constructor, is the one from the AsyncFileContext
public class ContactList : Gtk.ListBox{
private ListModel list_model;
public ContactList(IObservableCollection<IVCard> ivcards){
this.list_model = new VCardListModel(ivcards);
bind_model(this.list_model, create_row_func);
list_model.items_changed.connect(() => {
message("Items Changed!");
base.show_all();
});
}
private Gtk.Widget create_row_func(Object item){
return new ContactRow((IVCard)item);
}
}
Heres the way i 'solved' it.
I'm not particularly proud of this solution, but there are a couple of awful things about the Gtk ListBox, one of them being (and this might really be more of a ListModel issue) if the ListBox is bound to a ListModel, the ListBox will NOT be sortable by using the sort method, and to me at least, that is a dealbreaker. I've solved it by making a class which is basically a List wrapper, which has an 'added' signal and a 'remove' signal. Upon adding an element to the list, the added signal is then wired, so it will create a new Row object and add it to the list box. That way, data is control in a manner Similar to ListModel binding. I can not make it work without calling the ShowAll method though.
private IObservableCollection<IVCard> _ivcards;
public IObservableCollection<IVCard> ivcards {
get{
return _ivcards;
}
set{
this._ivcards = value;
foreach(var card in this._ivcards){
base.prepend(new ContactRow(card));
}
this._ivcards.item_added.connect((item) => {
base.add(new ContactRow(item));
base.show_all();
});
base.show_all();
}
}
Even though this is by no means the best code I've come up with, it works very well.
I'm trying to write a C++/CLI forms application that creates a lot of buttons at runtime: I have a vector of strings and for each string a button is being created:
std::vector<std::string> strings;
/*
string being initialized with values from file
*/
for ( std::vector<std::string>::iterator it = heroes.begin(); it != heroes.end(); ++it ) {
Button ^ button = gcnew Button;
/*
button being customized depending on the string
*/
buttonPannel->Controls->Add(button);
}
Now what I want to do is add an event handler for each button in a way that the string used to customize the button would be passed to handling method.
In c# I would have written something like
button->Click += new EventHandler((sender, args) => button_Click(s, e, *it));
How do I achieve this in C++/CLI?
You could do the exact equivalent of your C# code, but I'd rather make use of an existing property on the Button class to hold the extra data you need.
In this case, the Tag property seems appropriate: its purpose is to hold any extra data you need that is closely associated with the control, so this seems on-point for your string that drives the program logic. (You may need to make it a managed String^ object, rather than a std::string, but that's an easy conversion.)
void Form1::CreateButtons()
{
for (std::vector<std::string>::iterator it = heroes.begin(); it != heroes.end(); ++it)
{
Button ^ button = gcnew Button;
button->Tag = marshal_as<String^>(*it);
button->Click += gcnew EventHandler(this, &Form1::button_Click);
buttonPanel->Controls->Add(button);
}
}
void Form1::button_Click(Object^ sender, EventArgs^ e)
{
Control^ senderControl = dynamic_cast<Control^>(sender);
String^ heroName = nullptr;
if(senderControl != nullptr)
heroName = dynamic_cast<String^>(senderControl->Tag);
if(heroName == nullptr)
{
// Something went wrong. Bail out.
return;
}
// ...
}
If you really do want to do the equivalent of your C# code: Your C# lambda is doing variable capture on the it variable. We can do variable capture in C++/CLI, it's just a lot more manual.
(Note: Your C# example is capturing the iterator, not the string, not sure if that's what was intended. I wrote this to capture the string object instead.)
ref class EventHandlerStringCapture
{
public:
EventHandlerStringCapture(std::string str,
Action<Object^, EventArgs^, std::string>^ handler)
{
this->str = str;
this->handler = handler;
}
void eventHandler(Object^ sender, EventArgs^ e)
{
this->handler(sender, e, this->str);
}
private:
std::string str;
Func<Object^, EventArgs^, std::string>^ handler;
}
void Form1::CreateButtons()
{
for (std::vector<std::string>::iterator it = heroes.begin(); it != heroes.end(); ++it)
{
Button ^ button = gcnew Button;
// The variable to capture.
std::string str = *it;
// The actual event handler: a method in the current class.
Action<Object^, EventArgs^, std::string>^ actualHandler =
gcnew Action<Object^, EventArgs^, std::string>(this, &Form1::button_Click);
// Pass both the variable to capture and the
// actual event handler to a helper object.
EventHandlerStringCapture^ ehsc =
gcnew EventHandlerStringCapture(str, actualHandler);
// Grab the two-parameter event handler from the helper object,
// and make that the click handler.
button->Click +=
gcnew EventHandler(ehsc, &EventHandlerStringCapture::eventHandler);
buttonPanel->Controls->Add(button);
}
}
void Form1::button_Click(Object^ sender, EventArgs^ e, std::string heroName)
{
// ...
}
(Note: I'm not at a compiler, so there may be syntax errors.)
Obviously, using an existing property on the button object is simpler, but that's the C++/CLI equivalent to what the C# compiler does behind the scenes.
I have a recursive method like this:
protected void executeAction( TreeItem ti )
{
boolean isChecked = ti.getChecked();
if ( isChecked )
{
Somedata data = (SomeData) ti.getData();
String action = data.getSelectedAction();
ActionManager am = data.getActionManager();
AbstractActionAgent agent = am.getAction( action );
if ( agent != null )
{
agent.updateModel( data ); //Makes a server trips and long computation
}
}
int itemcnt = ti.getItemCount();
TreeItem[] childTrees = ti.getItems();
for ( int i = 0; i < itemcnt; i++ )
{
executeAction( childTrees[i] );
}
}
My updateModel method freezes the UI, so I tried using Job, but my problem is that I want the update model to be executed for checked TreeItem only and it should follow the sequence of checked TreeItems. If I use Job, I have no control over which checked TreeIem is processed first. Also I tried putting the whole executeAction method in a Job, but ran into invalid thread while accessing the TreeItem.
I need some ideas so that I can spwan a new thread while maintaining the sequence and not freezing my UI.
Thanks.
You could try this. Collect you model objects in a Tree and run that updateModel in a separate job. The below code doesn't run. You may need to tweak it a bit.
class Node {
private SomeData nodeData;
private List<Node> children = new ArrayList();
// Create getters, setters..
}
protected void executeAction( TreeItem ti, Node parentNode ) {
boolean isChecked = ti.getChecked();
Node n = null;
if ( isChecked )
{
Somedata data = (SomeData) ti.getData();
if (parentNode != null) {
n = new Node();
n.setNodeData(data);
parentNode.addChild(n);
}
}
int itemcnt = ti.getItemCount();
TreeItem[] childTrees = ti.getItems();
for ( int i = 0; i < itemcnt; i++ )
{
executeAction( childTrees[i],n );
}
}
I have a cell table with an async data provider. If I update the data via the data provider the table renders the new data correctly but the selection model still holds onto and returns old objects.
Any ideas how to refresh the selection model?
I think you should make your SelectionModel work with different instance of the same "logical" object using the appropriate ProvidesKey. For instance, you could use ProvidesKey that calls getId on the object, so that two objects with the same such ID would be considered equal; so even if the SelectionModel holds onto the old object, it can still answer "yes, it's selected" when you give it the new object.
FYI, this is exactly what the EntityProxyKeyProvider does (using the stableId of the proxy). And the SimpleKeyProvider, used by default when you don't specify one, uses the object itself as its key.
I came across the same issue. Currently I have this as single selection model.
SelectedRow = store it when you select it.
Then when data is reloaded you can clear it by
celltable.getSelectionModel().setSelected(SelectedRow, false);
I guess it is too late for you but hope it helps someone else.
Here is my manual method for refreshing the SelectionModel. This allows you to use the selectedSet() when needed and it will actually contain the current data, rather than the old data - including the removal of deleted rows and updated fields!
I have included bits & pieces of a class extending DataGrid. This should have all the logic at least to solve your problems.
When a row is selected, call saveSelectionKeys().
When the grid data is altered call refeshSelectedSet().
If you know the key type, you can replace the isSameKey() method with something easier to deal with. This class uses generics, so this method attempts to figure out the object conversion itself.
.
public abstract class AsyncDataGrid<T> extends DataGrid<T> {
...
private MultiSelectionModel<T> selectionModel_;
private ListDataProvider<T> dataProvider_;
private List<T> dataList_;
private Set<Object> priorSelectionKeySet_;
private boolean canCompareKeys_;
...
public AsyncDataGrid( final ProvidesKey<T> keyProvider ){
super( keyProvider );
...
dataProvider_ = new ListDataProvider<T>();
dataList_ = dataProvider_.getList();
canCompareKeys_ = true;
...
}
private void saveSelectionKeys(){
priorSelectionKeySet_ = new HashSet<Object>();
Set<T> selectedSet = selectionModel_.getSelectedSet();
for( Iterator<T> it = selectedSet.iterator(); it.hasNext(); ) {
priorSelectionKeySet_.add( super.getValueKey( it.next() ) );
}
}
private void refeshSelectedSet(){
selectionModel_.clear();
if( priorSelectionKeySet_ != null ){
if( !canCompareKeys_ ) return;
for( Iterator<Object> keyIt = priorSelectionKeySet_.iterator(); keyIt.hasNext(); ) {
Object priorKey = keyIt.next();
for( Iterator<T> it = dataList_.iterator(); it.hasNext(); ) {
T row = it.next();
Object rowKey = super.getValueKey( row );
if( isSameKey( rowKey, priorKey ) ) selectionModel_.setSelected( row, true );
}
}
}
}
private boolean isSameRowKey( final T row1, final T row2 ) {
if( (row1 == null) || (row2 == null) ) return false;
Object key1 = super.getValueKey( row1 );
Object key2 = super.getValueKey( row2 );
return isSameKey( key1, key2 );
}
private boolean isSameKey( final Object key1, final Object key2 ){
if( (key1 == null) || (key1 == null) ) return false;
if( key1 instanceof Integer ){
return ( ((Integer) key1) - ((Integer) key2) == 0 );
}
else if( key1 instanceof Long ){
return ( ((Long) key1) - ((Long) key2) == 0 );
}
else if( key1 instanceof String ){
return ( ((String) key1).equals( ((String) key2) ) );
}
canCompareKeys_ = false;
return false;
}
}
I fixed my particular issue by using the following code to return the visible selection. It uses the selection model to determine what is selected and combines this with what is visible. The objects themselves are returned from the CellTable data which is always upto date if the data has ever been changed via an async provider (the selection model data maybe stale but the keys will be correct)
public Set<T> getVisibleSelection() {
/*
* 1) the selection model contains selection that can span multiple pages -
* we want to return just the visible selection
* 2) return the object from the cellTable and NOT the selection - the
* selection may have old, stale, objects if the data has been updated
* since the selection was made
*/
Set<Object> selectedSet = getKeys(selectionModel.getSelectedSet());
List<T> visibleSet = cellTable.getVisibleItems();
Set<T> visibleSelectionSet = new HashSet<T>();
for (T visible : visibleSet) {
if (selectedSet.contains(KEY_PROVIDER.getKey(visible))) {
visibleSelectionSet.add(visible);
}
}
return visibleSelectionSet;
}
public static Set<Object> getKeys(Collection<T> objects) {
Set<Object> ids = new HashSet<Object>();
for (T object : objects) {
ids.add(KEY_PROVIDER.getKey(object));
}
return ids;
}