I have a form which has a Qtableview with a QSqlRelationalTableModel. I would like two of the columns to be read only, one to be a combobox and one to be a spinbox but with a specific range (no negative numbers.)
Out of the box without a QSqlRelationalDelegate, the view works with a combobox automatically on the third column and a spinbox. In trying to put the validation in the spinbox, I found that it solved my readonly problem on the first two columns. My only problem is how to correctly implement the combobox on the related field with the foreign key. I have been able to get a combobox to display but I can't see how to populate the choices from the foreign keys.
How do I populate the combo box?
See the code
def setup_rates_view(self):
self.rates_model = QSqlRelationalTableModel(self)
self.rates_model.setTable("rates_stay")
self.rates_model.setRelation(6, QSqlRelation("rates", "rate_code", "rate_description"))
self.rates_model.setFilter("id_bookings = '{0}'".format(myapp.res_id))
self.rates_model.setHeaderData(1, Qt.Horizontal, "Date")
self.rates_model.setHeaderData(2, Qt.Horizontal, "Basis")
self.rates_model.setHeaderData(6, Qt.Horizontal, "Code")
self.rates_model.setHeaderData(4, Qt.Horizontal, "Amount")
self.rates_model.select()
self.rates_view = self.ui.rates
self.rates_view.setModel(self.rates_model)
self.rates_view.setItemDelegate(QSqlRelationalDelegate(self.rates_view))
self.rates_view.setColumnHidden(0, True)
self.rates_view.setColumnHidden(3, True)
self.rates_view.setColumnHidden(5, True)
self.rates_view.horizontalHeader().moveSection(4,6)
self.rates_view.resizeColumnsToContents()
self.rates_view.setItemDelegate(Viewrates(self))
class Viewrates(QSqlRelationalDelegate):
def __init__(self, parent=None):
QItemDelegate.__init__(self)
def createEditor(self, parent, option, index):
if index.column() == 4:
spinbox = QSpinBox(parent)
spinbox.setRange(0,10000)
return spinbox
elif index.column() == 6:
combo = QComboBox(parent)
combo.setModel()
return combo
Related
I'm creating a custom module in odoo and I'm struggling with an inheritance issue, let's say i have the following implementation :
class SuperModel(models.Model) :
_name="model_name"
_inherits={'model_name.one':'model_name_one_id',
'model_name.two':'model_name_two_id'}
selection = fields.Selection(selection=[('m1','Model one'),('m2','Model Two')])
model_name_one_id = fields.Many2one(comodel_name="model_name.one",ondelete="cascade")
model_name_two_id = fields.Many2one(comodel_name="model_name.two",ondelete="cascade")
class ModelOne(models.Model):
_name="model_name.one"
value_one = fields.Char()
class ModelTwo(models.Model):
_name="model_name.two"
value_two = fields.Char()
What i want to achieve, is by selecting "Model 1" or "Model 2" in the main model view, only the corresponding fields will be displayed and stored in the database.
But whenever i create a record for "SuperModel" both records are created in "ModelOne" and "ModelTwo" tables.
For example if i select "Model 1" and fill "value_one", when saving, an empty record is created in "Model 2" table (model_name_two_id == False). How can i prevent that ?
Thank you for helping :)
OK using Delegate is impossible in you condition because odoo will make sure that the
many2one must have a value or the saving will not happen so use related field like this
class SuperModel(models.Model) :
_name="model_name"
selection = fields.Selection(selection=[('m1','Model one'),('m2','Model Two')])
# indecate that the Many2one are delegated = true
model_name_one_id = fields.Many2one(comodel_name="model_name.one",ondelete="cascade", )
model_name_two_id = fields.Many2one(comodel_name="model_name.two",ondelete="cascade", )
value_one = fields.Char(related="model_name_one_id.value_one")
value_two = fields.Char(related="model_name_two_id.value_two")
#api.model
def create(self, vals):
if not rec_id.value_one:
# if the related field of model_name_one_id are no null
# create a record from that relateds fields add it to vals
# i used vals directly odoo is smart to ignore the non existing field in model_name.one
# or iterate the vals and extract a dictionary of model_name.one
m2on_rec = self.env['model_name.one'].create(vals) # create a record
vals.update({'model_name_one_id':m2on_rec.id}) # add the id to vals
return super(SuperModel, self).create(vals)
elif not rec_id.value_one:
# same thing for the second many2one
else :
# show error or create a simple record
return return super(SuperModel, self).create(vals)
#api.one # is used one so i make sure that self containing only one record it's hard for multi need to much code
def write(self, vals):
# check if any of the related field of model_name_one_id is changed
if any(field_name in self.env['model_name.one'] for field_name in vals.keys()) :
# then check the many2one field all ready have a value so the operation here is update
if self.model_name_one_id:
return super(SuperModel, self).write(vals) # related field will do the changes
else :
# here we need to delete the record of model_name_two_id
self.model_name_two_id.unlink()
# here the same thing in create you need to create the record
retrun super(SuperModel, self).write(vals)
else :
# same thing for model_name_two_id
i tried this solution and it work sooo fine just create the record of the one2many field it's like you are the one who are delegating not the frame work for editing is more complex because you need to delete the record and then save the new one
My Problem:
This is my form
I would like to just filter out same ID rows
Barely any code to show pretty much dragged and dropped entity-es and did some customizing.
Toolstrip and datagrid are binded to same source
My Goal:
I would like my form to filter out only the rows with same ID as ID in textbox when I navigate with toolbox buttons
My thoughts so far: Perhaps I can make a query where ID in textbox matches ID in first column and weed out those rows and just display them. But how would that keep the format I have? Comboboxes etc.. some columns have different binding sources, for example column #2, column#3 are have different binding sources as opposed to column#1(to which i'm trying to filter rows) and column#4,5
Edit: I have tried following
nastavniPlanProgramDataGridView.CurrentCell = null;
foreach (DataGridViewRow row in nastavniPlanProgramDataGridView.Rows)
{
if(row.Index!=rowIndex)
{
row.Selected = false;
row.Visible = false;
}
else
{
if(row.Index==rowIndex)
{
row.Selected = true;
row.Visible = true;
}
}
However i keep encountering an error when executing row.Visible=false;
Solved by simply adding the following before executing the above code-snippet in the question.
AllowUserToAddRows = false;
I am working on WPF Datagrid where we are customizing the grid to support functionalities like search, filter, column Hiding/Un-Hiding etc,
For Filter and Search we are using CollectionViewSource to get the ICollectionView and on that list we are performing various operations mentioned above.
For a particular Functionality like search, User had reordered the columns and then performed search operation, i am getting the ICollectionView data using the below function:
ICollectionView objDatacollectionview =
CollectionViewSource.GetDefaultView(DataSource.DataGridSrcCollection);
DataSource.DataGridSrcCollection is my ObservableCollection<DataRowView>.
From the objDataCollectionView object, I get the itemArray of each row and search for the text and start highlighting the cell where the text is found, but the problem I am facing is my CollectionViewSource object always returns the DataRow ItemArray in the original order when the Grid is loaded, as it is not considering the Column Reordering, the search is not pointing to the first column, it is actually pointing to the column which was the first column when loaded for the first match text.
The question I had is , is there a way to get the item array value in the correct order on how the columns are re-ordered, if not can I use any other approach with Collectionview so that I can highlight the correct item when search is done.
Below is the sample code i had
below is the sample code
objDatacollectionview = CollectionViewSource.GetDefaultView(DataSource.DataGridSrcCollection)
// Initially get all the DataRowview ItemArray , Here i get all the rows itemarray text as it is
var temp = (from s in objDatacollectionview.OfType<DataRowView>()
select s.Row.ItemArray).ToList();
for (int i = 0; i < temp.Count(); i++)
{
// Now in each item array , i search for the string and will get the list of the strings that match the searchText
var cellitems = temp[i].Where(x => x.ToString().Contains(searchText, StringComparison.OrdinalIgnoreCase)).
Select(x => x).ToList();
//This is to point to the row where there is first occurance of the searched text.This will execute only for the first time
if (cellitems.Count() != 0 && !IsfirstSelectedRowIndex)
{
DataRowView dv = objDatacollectionview.OfType<DataRowView>().ElementAt(i);
objDatacollectionview.MoveCurrentTo(dv);
int cellIndex = temp[i].Select((Value, Index) => new { Value, Index }).Single(p => p.Value.Equals(cellitems[0])).Index;
DataGridCellInfo cellinfo = new DataGridCellInfo(cellitems[0], batchDataGrid.Columns[cellIndex]);
batchDataGrid.CurrentCell = cellinfo;
IsfirstSelectedRowIndex = true;
}
} `
In the above code as i am moving through each row itemArray data i have is the original order, not the re-ordered columns, i can use the index to column mapping, but after this operation i have to support previous search item and next search item, so i am thinking that would impact the performance if i am doing search with column display index every time.
Each DataGridColumn has a DisplayIndex property, which will tell you the current position of the column. This will allow you to determine the ordering.
In my application I hava combobox which is holding some values from databse ( some real time updated list ). This ComboBox list is updated every 1 minute.
List dosen't have null values. When I'm setting this list to ComboBox..
ComboBox box = new ComboBox(items);
.. there is one extra "empty" row, which is perfectly fine because non value is selected.
Right after I'm selecting some value my "empty" value disappears from the list.
My main question is How to keep this value on the list?
Why this is a problem..
Scenerio values is selected in database, first application start
List is loaded ( wiht selected empty value ).
Value is selected.
During first background refresh, empty values disappears, and combobox value is selected to n+1, next value is selected.
If I want to select empty values I have to all clearSelection from selection model / by some extra control.
While it is an old question, I spent quite bit of time trying to solve the same issue in my application and thought i might as well add my solution here.
One possible workaround is to create an extra list that contains null and the items you wish to be selectable.
ObservableList<String> selectableActivities = FXCollections.observableArrayList("a", "b", "c");
ObservableList<String> selectableActivitiesWithNull = FXCollections.observableArrayList();;
selectableActivitiesWithNull.add(null);
selectableActivitiesWithNull.addAll(selectableActivities);
In order to support updates of the original list you would need a ListChangeListener that updates the extra list according to changes in the original list.
selectableActivities.addListener((ListChangeListener<String>)(change -> {
while (change.next()) {
if (change.wasPermutated()) {
selectableActivitiesWithNull.sort((a, b) -> {
return Integer.compare(selectableActivities.indexOf(a), selectableActivities.indexOf(b));
});
} else if (change.wasRemoved()) {
selectableActivitiesWithNull.remove(change.getFrom()+1, change.getTo()+2);
} else if (change.wasAdded()) {
selectableActivitiesWithNull.addAll(change.getFrom()+1, selectableActivities.subList(change.getFrom(), change.getTo()));
} else if (change.wasUpdated()) {
for (int i = change.getFrom(); i < change.getTo(); ++i) {
selectableActivitiesWithNull.set(i+1, selectableActivities.get(i));
}
}
}
}));
And finally you use the extra list for the ComboBox items.
ComboBox<String> combobox = new ComboBox<String>();
combobox.setItems(selectableActivitiesWithNull);
Now you can modify the original list as usual and the ComboBox will update accordingly while also having an empty selection as the first item. And most importantly your original list will not be polluted by placeholder objects that could cause issues in other parts of the application.
This will also work with other objects, assuming that you add an apropriate StringConverter to the ComboBox. Note that the converter must also be able to handle null values if using the above approach.
StringConverter<Object> activityConverter = new StringConverter<Object>() {
#Override
public String toString(Object object) {
return object != null ? object.toString() : "";
}
#Override
public ActivityDataRow fromString(String string) {
return null;
}
};
combobox.setConverter(activityConverter);
While this approach is not exactly what you desired, I believe this is a close you can get without implementing a custom combobox.
I have two list that I load when my app starts. The first one loads a complete set of data from the database, the second one independently loads a set of associated data from file.
Each list is loaded into a BindingSource and set as the DataSource for their respective combobox. The data is loading just fine.
The issue is that I need to have the second comboBox only display the elements of its list that correspond with the selected value of the first list.
I have attempted to set the value members to the referential bit of data, but cant figure out how to get the comboBoxSettings to only show the the items whose EventID matches the selected item's EventID from the EventList comboBox.
//Event List comboBox
comboBoxEventList.DataSource = _eventSimPresenter.BindingSourceEventList;
comboBoxEventList.DisplayMember = "DisplayName";
comboBoxSettings.ValueMember = "EventID";
//Settings combobox
comboBoxSettings.DataSource = _eventSimPresenter.BindingSourceUserSettings;
if (_eventSimPresenter.BindingSourceUserSettings.Count > 0)
{
comboBoxSettings.DisplayMember = "EventName";
comboBoxSettings.ValueMember = "EventID";
}
thanks!
You can reate a method in _eventSimPresenter that return a BindingSourceUserSettings by eventId. When the 1st comboBox changes, take the selected eventId and update 2nd comboBox datasource:
...
comboBoxSettings.DataSource =
_eventSimPresenter.GetBindingSourceUserSettings(selectedEventId)
if (_eventSimPresenter.BindingSourceUserSettings.Count > 0)
{
comboBoxSettings.DisplayMember = "EventName";
comboBoxSettings.ValueMember = "EventID";
}
In other terms, the filtering should be applied to the datasource since it's not possible to do it via the comboBox.