PostEditChangeProperty called with nullptr Property and MemberProperty?

Hello everyone!

As the title says, I’m currently struggeling with PostEditChangeProperty. Below is a small class derived from AActor that shows the problem.

I’m using 4.9.1 and when I edit the properties under the Test Category for an instance of (A)MyActor placed in the level via the details panel by clicking and dragging left or right or up or down it works as expected. However when I do the same in a blueprint that subclasses AMyActor the last call to PostEditChangedProperty will pass me nullpointers for both PropertyChangedEvent::Property and PropertyChangedEvent::MemberProperty.

Am I doing something wrong or is this intended?

Digging a little deeper I can also see that PostEditChangeProperty is called multiple times even if I enter a specific value with the keyboard and confirm it by pressing enter in the Blueprint details panel (not for an instance in the level). I haven’t tested whether the value is updated correctly, or at what point between the calls the update happens. If it’s already covered by the first call then I guess the first problem is not really an issue, just that my code reacting to changes in properties runs multiple times even though it wouldn’t really be necessary (i.e. for no reason?).

My final observation is that when recompiling the Blueprint PostEditChangeProperty is only called once, with nullpoints for both PropertyChangedEvent::Property and PropertyChangedEvent::MemberProperty which could potentially be problematic again, although when recompiling the value won’t change at the same time.

Placing an instance of the Blueprint in the level then works as expected again when editing the properties of that instance.

All ideas and discussion are very much appreciated. Thank you.

.h


#pragma once

#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

UCLASS()
class MYPROJECT_API AMyActor : public AActor
{
	GENERATED_BODY()
	
public:
	AMyActor();

#if WITH_EDITOR
	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;

	uint64 GetKey(uint64 Key, uint64 Offset);
	void PrintPropertyDetails(UProperty* Property, FString Name, uint64 Key);
#endif

	UPROPERTY(Category = "Test", EditAnywhere)
	FRotator Rotation;
	UPROPERTY(Category = "Test", EditAnywhere)
	FQuat Quaternion;
	UPROPERTY(Category = "Test", EditAnywhere)
	FVector4 Vector4;
	UPROPERTY(Category = "Test", EditAnywhere)
	float Value;

};

.cpp


#include "MyProject.h"
#include "MyActor.h"


AMyActor::AMyActor()
{
}

#if WITH_EDITOR
uint64 AMyActor::GetKey(uint64 Key, uint64 Offset)
{
	//replace with "return -1;" if you want to see the history of on screen debug messages
	return Key + Offset;
}

void AMyActor::PrintPropertyDetails(UProperty* Property, FString Name, uint64 Key)
{
	if (GEngine)
	{
		if (Property)
		{
			FString PropertyCppName = Property->GetNameCPP();
			FString PropertyName = Property->GetName();
			GEngine->AddOnScreenDebugMessage(GetKey(Key, 0), 5.0f, FColor::Black, FString::Printf(TEXT("AMyActor::PrintPropertyDetails called for %s with cpp name %s and name %s"), *Name, *PropertyCppName, *PropertyName));

			UProperty* OwnerProperty = Property->GetOwnerProperty();
			if (OwnerProperty)
			{
				FString OwnerPropertyCppName = OwnerProperty->GetNameCPP();
				FString OwnerPropertyName = OwnerProperty->GetName();
				GEngine->AddOnScreenDebugMessage(GetKey(Key, 1), 5.0f, FColor::Black, FString::Printf(TEXT("AMyActor::PrintPropertyDetails owner property with cpp name %s and name %s"), *OwnerPropertyCppName, *OwnerPropertyName));
			}
			else
			{
				GEngine->AddOnScreenDebugMessage(GetKey(Key, 1), 5.0f, FColor::Black, TEXT("AMyActor::PrintPropertyDetails does not have an owner property!"));
			}

			UField* OuterField = Property->GetOuterUField();
			if (OuterField)
			{
				GEngine->AddOnScreenDebugMessage(GetKey(Key, 2), 5.0f, FColor::Black, FString::Printf(TEXT("AMyActor::PrintPropertyDetails outer field full name %s"), *(OuterField->GetFullName())));
			}
			else
			{
				GEngine->AddOnScreenDebugMessage(GetKey(Key, 2), 5.0f, FColor::Black, TEXT("AMyActor::PrintPropertyDetails does not have an outer field!"));
			}

			UStruct* OwnerStruct = Property->GetOwnerStruct();
			if (OwnerStruct)
			{
				FString OwnerStructCppPrefix = OwnerStruct->GetPrefixCPP();
				GEngine->AddOnScreenDebugMessage(GetKey(Key, 3), 5.0f, FColor::Black, FString::Printf(TEXT("AMyActor::PrintPropertyDetails owner struct full name %s with cpp prefix %s"), *(OwnerStruct->GetFullName()), *(OwnerStructCppPrefix)));
			}
			else
			{
				GEngine->AddOnScreenDebugMessage(GetKey(Key, 3), 5.0f, FColor::Black, TEXT("AMyActor::PrintPropertyDetails does not have an owner struct!"));
			}
		}
		else
		{
			GEngine->AddOnScreenDebugMessage(GetKey(Key, 0), 5.0f, FColor::Black, FString::Printf(TEXT("AMyActor::PrintPropertyDetails called but Property %s is invalid nullptr!"), *Name));
			GEngine->AddOnScreenDebugMessage(GetKey(Key, 1), 5.0f, FColor::Black, TEXT(""));
			GEngine->AddOnScreenDebugMessage(GetKey(Key, 2), 5.0f, FColor::Black, TEXT(""));
			GEngine->AddOnScreenDebugMessage(GetKey(Key, 3), 5.0f, FColor::Black, TEXT(""));
		}
	}
}

void AMyActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
	Super::PostEditChangeProperty(PropertyChangedEvent);

	UProperty* MemberProperty = PropertyChangedEvent.MemberProperty;
	PrintPropertyDetails(MemberProperty, TEXT("MemberProperty"), 1);

	UProperty* Property = PropertyChangedEvent.Property;
	PrintPropertyDetails(Property, TEXT("Property"), 5);
}
#endif

I always forget this too


Super::PostEditChangeProperty(PropertyChangedEvent);

Good point and well spotted, I’ve edited the above. Still the problem remains (and now those multiple calls seem to have roughly doubled).

I figured out why they have doubled. When the Blueprint has its details changed, it will cause all instance of this Blueprint to also get updated with PostEditChangeProperty. And since I had placed one such instance I was getting twice as many calls as before (once for the main Blueprint and once for the instance).

The problems described in my initial post still remain though.

EDIT: When I’m getting nullpointer properties, ChangeType is Unspecified although I have clearly just set the value by entering it and pressing enter.

Thanks.