I'm doing my controller. When I click on an item in my ListWidget, the following method is called :
void ContactBookController::setCurrentlySelectedItem(QListWidgetItem *item)
{
setCurrentItem(*item);
m_window.setEditButtonsState(true);
}
And the setCurrentItem() method is as follows : (m_current_item is a private variable):
void ContactBookController::setCurrentItem(const QListWidgetItem ¤t_item)
{
m_current_item = current_item;
}
Now, when I create a contact, I add an item to the QListWidget, but I also create a Contact object, but I also bind them together in a QHash so I know what QListWidgetItem corresponds to what Contact. In short, here is what I did :
void ContactBookController::createContact()
{
auto new_contact = m_contact_book.createContact();
if(new_contact != nullptr)
{
new_contact->setName(tr("New contact"));
}
auto list_item = m_window.createContact(new_contact->getName());
m_window.clearSelection();
m_contact_map.insert(list_item, new_contact);
}
Now, when clicking on a QListWidgetItem, I activate the edit button and I would like to retrieve the corresponding Contact object. However, when doing this, the Contact object doesn't seem to be correct. Indeed, if I use a method on him (like getName()), my application instantly crashes.
Here is what I did :
void ContactBookController::editContact()
{
auto list_item = m_current_item;
auto contact = m_contact_map.value(&list_item); /* doesn't work */
}
My hash table is declared as such :
QHash<QListWidgetItem*, Contact*> m_contact_map;
Any idea what I did wrong ?
Your hash has the type QHash<QListWidgetItem*, Contact>. So, mapping a item pointer to a contact value.
When you save the mapping for a specific item with m_contact_map.insert(list_item, new_contact), you add a mapping from the item pointer to the contact. But when you try to retrieve the contact with m_contact_map.value(&list_item), you look up the value for the pointer to the local list_item variable, which points to somewhere on the stack, and has nothing to do with the pointer to the item that is shown in the view.
Either you need to save to pointer to the selected value, i.e. make m_current_item a QListWidgetItem *m_current_item, or you simply use QListWidget::currentItem() to retrieve the current item without the need for an additional member variable.
(Side note: You should check for currentItem() != nullptr, since a list widget does not necessarily has an item selected.)
Related
In my Unreal Engine project, which uses GameplayAbilitySystem and the default server->client architecture, the client gets notified of an attribute value changes that happened on the server.
Additionally, I'm trying to get not only the new value, but also the amount the value changed (delta = new value - old value). This should be possible using the attribute value change delegate, since it contains FOnAttributeChangeData with its members NewValue and OldValue.
On the server, both values are correct. However, on the client, FOnAttributeChangeData::NewValue == FOnAttributeChangeData::OldValue and both have the value being identical to NewValue on the server.
This is because the delegate is called after the replication happened ...
UPROPERTY(ReplicatedUsing=OnRep_MyAttribute)
FGameplayAttributeData MyAttribute;
void UAttributeSetBase::OnRep_MyAttribute()
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, MyAttribute);
}
(this is default GAS setup of ActionRPG)
... so the client has no knowledge about the value it had before replication.
How do I get the value of the attribute, which it had before it got updated by the server?
How do I forward this value to the delegate?
Getting the old value (question 1)
UnrealEngine OnRep functions provide the previous state of an replicated variable as first parameter in the OnRep function. So add the parameter
void UAttributeSetBase::OnRep_MyAttribute(const FGameplayAttributeData& Previous)
{
const auto PreviousValue = Previous.GetCurrentValue(); // See below for possible usage.
GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, MyAttribute);
}
Thanks #Dan from Unreal GAS discord channel.
Forward the value to the delegate (question 2)
Idea
When your goal is to not modify the UE4 source code, one possibility is to cache the previous value within the attribute set, so you are able to access it from outside.
Cache that value for each attribute in the attribute set OnRep function.
Use the cached value in the delegate, but only if it is valid. Since the value is assigned within the OnRep function, it won't exist on the server. This is perfectly fine since we want to retain the behaviour on the server, which uses FOnAttributeChangeData::OldValue (which has the correct value only on the server).
Example implementation
Caching the previous value
AttributeSetBase.h:
// Wrapper for a TMap. If you need thread safety, use another container or allocator.
class CachePreviousDataFromReplication
{
TMap<FName, FGameplayAttributeData> CachedPreviousData;
public:
void Add(const FName, const FGameplayAttributeData&);
auto Find(const FName) const -> const FGameplayAttributeData*;
};
class YOUR_API UAttributeSetBase : public UAttributeSet
{
// ...
private:
UFUNCTION() void OnRep_MyAttribute(const FGameplayAttributeData& Previous);
// ...
private:
CachePreviousDataFromReplication CachedDataFromReplication;
public:
// \param[in] AttributeName Use GET_MEMBER_NAME_CHECKED() to retrieve the name.
auto GetPreviousDataFromReplication(const FName AttributeName) const -> const FGameplayAttributeData*;
}
AttributeSetBase.cpp:
void CachePreviousDataFromReplication::Add(const FName AttributeName, const FGameplayAttributeData& AttributeData)
{
this->CachedPreviousData.Add(AttributeName, AttributeData);
}
auto CachePreviousDataFromReplication::Find(const FName AttributeName) const -> const FGameplayAttributeData*
{
return CachedPreviousData.Find(AttributeName);
}
void UAttributeSetBase::OnRep_MyAttribute(const FGameplayAttributeData& Previous)
{
CachedDataFromReplication.Add(GET_MEMBER_NAME_CHECKED(UAttributeSetBase, MyAttribute), Previous); // Add this to every OnRep function.
GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, MyAttribute);
}
auto UAttributeSetBase::GetPreviousDataFromReplication(const FName AttributeName) const -> const FGameplayAttributeData*
{
return CachedDataFromReplication.Find(AttributeName);
}
Accessing the previous value in the delegate
ACharacterBase.h:
class YOUR_API ACharacterBase : public ACharacter, public IAbilitySystemInterface
{
// ...
void OnMyAttributeValueChange(const FOnAttributeChangeData& Data); // The callback to be registered within GAS.
// ...
}
ACharacterBase.cpp:
void ACharacterBase::OnMyAttributeValueChange(const FOnAttributeChangeData& Data)
{
// This delegate is fired either from
// 1. `SetBaseAttributeValueFromReplication` or from
// 2. `InternalUpdateNumericalAttribute`
// #1 is called on clients, after the attribute has changed its value. This implies,
// that the previous value is not present on the client anymore. Therefore, the
// value of `Data.OldValue` is erroneously identical to `Data.NewValue`.
// In that case (and only in that case), the previous value is retrieved from a cache
// in the AttributeSet. This cache will be only present on client, after it had
// received an update from replication.
auto deltaValue = 0.f;
if (Data.NewValue == Data.OldValue)
{
const auto attributeName = GET_MEMBER_NAME_CHECKED(UAttributeSetBase, MyAttribute);
if (auto previousData = AttributeSetComponent->GetPreviousDataFromReplication(attributeName))
{
// This will be called on the client, when coming from replication.
deltaValue = Data.NewValue - previousData->GetCurrentValue();
}
}
else
{
// This might be called on the server or clients, when coming from
// `InternalUpdateNumericalAttribute`.
deltaValue = Data.NewValue - Data.OldValue;
}
// Use deltaValue as you like.
}
I have a list of application specific items uniquely identified by an id. Their names are displayed in a QTreeWidget (one item corresponds to a single QTreeWidgetItem). I would like to somehow attach the corresponding ids to these QTreeWidgetItems so that upon selection changed I can access the id of the corresponding item and do some processing.
QTreeWidgetItem does not inherit from QObject so I cannot use its setProperty function. How could I do this?
Just create some user defined roles for the properties...
typedef enum {
id_1_role = Qt::UserRole,
id_2_role,
id_N_role,
} property_id_role;
Then you can use the normal means of getting/setting the data associated with a QTreeWidgetItem.
QTreeWidgetItem *item = ...
/*
* Set the property value.
*/
item->setData(column, property_id_role::id_2_role, id_2_value);
/*
* Get the property value.
*/
auto id_2_value = item->data(column, property_id_role::id_2_role).value<id_2_type>();
Do you know that QTreeWidgetItem has a setData method?
setData(int column, int role, const QVariant &value)
You can use it with your roles. For example:
int your_id = 123;
ui->treeWidget->currentItem()->setData(0,Qt::UserRole,your_id);
qDebug() << ui->treeWidget->currentItem()->data(0,Qt::UserRole);
Unity3D 2018.2
So I have a canvas that gets deactivated and reactivated at users choice. I have a List filled with transforms in which I populated the Grid with. I also have another List to hold all the images that will be used to populate the Grid with, since what the grid is being populated with are buttons containing the transform. For example if I click the button and run Debug.Log("transform.name"), I should see all my different transforms.
Problem 1: Every time this canvas gets enabled or disabled, the grid
keeps adding 1 element under the initial one, and after being
enabled/disabled again, another element is instantiated under that
one.
+
Problem 2: Also none of these instantiated buttons get called. Maybe
the problem here is due to the 1st problem?
Here is the code I have for inside the button I have
public class SatelliteButton : MonoBehaviour {
[SerializeField]
private Image myIcon;
public void SetIcon(Sprite mySprite)
{
myIcon.sprite = mySprite;
}
public void OnMouseEnter()
{
Debug.Log(transform.name);
}
}
Here is the code for populating the grid
public class SatelliteGridControl : MonoBehaviour {
private List<Transform> satelliteListFromPlanet;
[SerializeField]
private GameObject buttonTemplate;
[SerializeField]
private GridLayoutGroup gridGroup;
[SerializeField]
private List<Sprite> iconSprites;
// Use this for initialization
void OnEnable()
{
getSatellitesInPlanet();
genInventory();
}
// Get Satellites
private void getSatellitesInPlanet()
{
satelliteListFromPlanet = new List<Transform>();
// Get current planet
Transform currentPlanet = GameObject.FindGameObjectWithTag("MainCamera").GetComponent<HandleCamera>().targetToLookAt;
// Check inside for satellites
foreach (Transform satellite in currentPlanet)
{
// Check transform for tag
if (satellite.CompareTag("Satellite"))
{
// Add each transform from planet to array
satelliteListFromPlanet.Add(satellite);
}
}
}
// Handle Grid
private void genInventory()
{
if (satelliteListFromPlanet.Count < 6)
{
gridGroup.constraintCount = satelliteListFromPlanet.Count;
}
else
{
gridGroup.constraintCount = 5;
}
foreach (Transform sat in satelliteListFromPlanet)
{
GameObject newButton = Instantiate(buttonTemplate) as GameObject;
newButton.SetActive(true);
newButton.GetComponent<SatelliteButton>().SetIcon(iconSprites[Random.Range(0, iconSprites.Count)]);
newButton.transform.SetParent(buttonTemplate.transform.parent, false);
}
}
}
I appreciate the help! Learning about grid layouts with List<>!
In genInventory() you Instantiate a new button for every Transform in satelliteListFromPlanet. Since only 1 is added it seems that your list only contains 1 element.
But you never destroy/remove this Instantiated GameObjects, so the next time you re-enable the object, OnEnable will be called again and new prefabs are Instantiated.
Solution:
Make sure to store the Instantiated objects somewhere e.g.
private List<GameObject> instances = new List<GameObject>();
//....
foreach (Transform sat in satelliteListFromPlanet)
{
GameObject newButton = Instantiate(buttonTemplate) as GameObject;
newButton.SetActive(true);
newButton.GetComponent<SatelliteButton>().SetIcon(iconSprites[Random.Range(0, iconSprites.Count)]);
newButton.transform.SetParent(buttonTemplate.transform.parent, false);
instances.Add(newButton);
}
And than you can destroy them when you disable the object:
private void OnDisable()
{
foreach(var instance in instances)
{
Destroy(instance);
}
}
Problem 1: Every time this canvas gets enabled or disabled, the grid keeps adding 1 element under the initial one, and after being enabled/disabled again, another element is instantiated under that one.
Right now you're calling getSatellitesInPlanet() on SatelliteGridControl's OnEnabled() method. This method is called every time the object is set to active. If you just want to call your method once, use Start.
Problem 2: Also none of these instantiated buttons get called. Maybe
the problem here is due to the 1st problem?
I don't think the problems are related. Can you confirm that your buttonTemplate prefab has an Image element and its RaycastTarget is enabled?
Just one tip, I would set the type of buttonTemplate to SatelliteButton, not GameObject. That way you can instantiate it as type SatelliteButton and avoid the need to call:
newButton.GetComponent<SatelliteButton>()
From various questions, I know it is impossible to create DataTemplate from the code behind without using the XamlReader. So I want to ask if there is a way to programatically generates the UI for each Item in a ListView. I don't seem to find any relevant event handler or member of ListView for this purpose. Ideally, I want the ListView to invoke my handler code to generate UI for each data item it needs to display.
Imitating the official XamlTreeView sample, I have tried overriding some ListView method such as PrepareContainerForItemOverride but this won't work. The solution I found is as #JustinXL suggests: producing ListViewItem and insert them to the ListView->Items directly
//assume that items is a list of items we want to bind
myListView->Items->Clear();
for(auto i : items)
{
ListViewItem^ v = ref new ListViewItem();
v->Content = GenerateUIFor(i);
myListView->Items->Append(v); // NOTE: a wrapping ListViewItem is required!
}
To support usual data binding, it would be best to make the data structure to cache the generated UI. For example,
ref class MyDataStructure
{
public:
property ListViewItem^ Item
{
ListViewItem^ get()
{
if (_item == nullptr)
GenerateUI();
return _item;
}
}
void GenerateUI()
{
_item = ref new ListViewItem();
_text_block = ref new TextBlock(); // sample
_item->Content = _text_block;
UpdateUI();
}
// Invoke this when changing the state of this object
void UpdateUI()
{
if (_text_block != nullptr) // sample
{
_text_block->Text = this->ToString(); // sample
}
}
private:
ListViewItem^ _item;
TextBlock^ _text_block;
};
The downside of this is of course we can't make use of data virtualization. But it works properly for small set of data. For large set, one can use website's approach with Next and Prev button to load next page or go back to previous page.
ItemContainerGenerator should let you construct the entire UI for an item inside a list view. Unfortunately there doesn't appear to be much in the way of non MSDN documentation/samples for this.
Alternatively if you can maintain a list of all the DataTemplates you might need to show, you could use a DataTemplateSelector to choose which DataTemplate you want to show for each individual item.
I need to select an item from a list box that contains objects of my class. Here is my code:
the load event:
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
XDocument loadedData = XDocument.Load("file.xml");
var data = from query in loadedData.Descendants("element")
select new myClass
{
First = (string)query.Element("first"),
Second = (string) query.Element("second")
};
List<myClass> d = data.ToList<myClass>();
myList = d;
myListBox.ItemsSource = data;
}
and then my button which is supposed to change the selected Item:
private void button1_Click(object sender, RoutedEventArgs e)
{
myListBox.SelectedItem = myList[100];
}
am I doing something wrong here?
myList isn't the collection you've bound to the list.
Either make d a wider scoped variable and refer to that in button1_click
or
store data in myList rather than a copy of it.
For efficiency, I would set the ItemsSource to 'd' rather than 'data'. The enumeration returned by the linq query is lazy, so it gets evaluated every time the UI updates. The array you have is eagerly created, so later lookups are fast.
Looks like a little bug here. I was able to inconstantly replicate a similar problem.
I used the standard VS template for a Databound Application. The template comes with some sample data and a populated list box. I added a button which set the selected item to the 15th item in the collection. This worked as expected. However, when I scrolled the list up to the top it wouldn't return to the 15th record when I clicked the button. Taking a guess here, what you're running into is another symptom of the same problem.
To solve this I first set the SelectedIndex to -1 which essentially deselects the item.
private void button1_Click(object sender, RoutedEventArgs e)
{
myListBox.SelectedIndex = -1;
myListBox.SelectedItem = myList[100];
}
I'm curious if this also solves your problem.