I have 5 sliders that all change value when 1 does. I’m doing a full round trip to the server and back to calculate the new values when 1 slider changes, and then the PlayerController on the original calling client will call a method on the parent widget that in turn calls SetValue()
on the 4 sliders that didn’t cause the change.
Unfortunately, I can’t find a way to set the slider values without triggering the OnValueChanged()
event for the 4 I’m setting, which triggers an infinite feedback look.
I would have thought there should either be a function called something like SetValueWithoutEvent()
, or have a boolean on the SetValue()
method to state without the event should be triggered. Is there nothing like this? Or is there something else obvious I’m missing?
I have read other pitched solutions, and none of them are viable.
One solution was to set a member var boolean to state whether the event should be called, and then reference and clear that inside the OnValueChanged()
event… but not only is that an incredibly ugle solution, it’s also potenitally going to lead to race condition like complications. This idea goes completely against what I know about writing code.
Another was to unbind and rebind the OnValueChanged()
event, but I can’t reference that within the function that’s calling SetValue()
, that can only be referenced directly inside the event graph. It also seems like an odd solution presumably with some minor overhead compared to just having a boolean or seperate event that avoids triggering the event…
- In the Editor menu, click on Tools/New C++ Class
- Click “All Classes”
- Type “Slider” in the search box.
- Scroll down to the row that says exactly “Slider” and select it.
- Click “Next”
- Give your new Slider a name.
- Click “Create Class”
Add this method so that your header looks something like this. MYGAME_API and your class name will be different as well as the last include.
#pragma once
#include "CoreMinimal.h"
#include "Components/Slider.h"
#include "MySlider.generated.h"
UCLASS()
class MYGAME_API UMySlider : public USlider
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void SetValueWithoutEvent(float InValue);
};
For your .cpp file, it should look like this. The first include will be your own class header. The second include needs to be added. Change UMySlider for your own class name.
#include "MySlider.h"
#include "Widgets/Input/SSlider.h"
void UMySlider::SetValueWithoutEvent(float InValue)
{
if (MySlider.IsValid())
{
MySlider->SetValue(InValue);
}
if (Value != InValue)
{
Value = InValue;
}
}
Compile this. Even live compile should work. It should now show up in your palette.
Ok… so I’m gathering from looking at UE’s slider code that MySlider
is the GUI component is it?
Trying to call MySlider->SetValue()
causes a linker error. MySlider.IsValid()
is fine, but SetValue()
is playing up.
> Slider2.cpp.obj : error LNK2019: unresolved external symbol "__declspec(dllimport) public: void __cdecl SSlider::SetValue(class TAttribute<float>)" (__imp_?SetValue@SSlider@@QEAAXV?$TAttribute@M@@@Z) referenced in function "public: void __cdecl USlider2::set_value(float,bool)" (?set_value@USlider2@@QEAAXM_N@Z)
> Hint on symbols that are defined and could potentially match:
> "__declspec(dllimport) public: void __cdecl USlider::SetValue(float)" (__imp_?SetValue@USlider@@QEAAXM@Z)
> D:\PeterBurcello\Documents\Unreal Projects\RTSMapGenerator\Binaries\Win64\UnrealEditor-RTSMapGenerator.dll : fatal error LNK1120: 1 unresolved externals
It’s hint ofcourse being the original SetValue()
I’m trying to avoid
If I remove what I suspect is the GUI update line that’s causing the linker error, I don’t see the slider GUI update, and I’m being warned not to use Value = x
directly…
warning C4996: 'USlider::Value': Direct access to Value is deprecated. Please use the getter or setter. Please update your code to the new API before upgrading to the next release, otherwise your project will no longer compile.
My implementation:
#pragma once
#include "CoreMinimal.h"
#include "Components/Slider.h"
#include "Slider2.generated.h"
/**
*
*/
UCLASS()
class RTSMAPGENERATOR_API USlider2 : public USlider
{
GENERATED_BODY()
UFUNCTION(BlueprintCallable)
void set_value(float in_value, bool trigger_event);
};
#include "Slider2.h"
void USlider2::set_value(float in_value, bool trigger_event)
{
if (trigger_event) {
USlider::SetValue(in_value);
return;
}
if (Value != in_value)
Value = in_value;
}
And all the same, this is a messy way to solve this problem too. Surely there’s a straight forward way directly in blueprints I can set the value without triggering the event and not have to create a whole new C++ class just to do this?
To fix the linker error, I had to update my [ProjectName].Build.cs
file to include Slate modules.
using UnrealBuildTool;
public class RTSMapGenerator : ModuleRules
{
public RTSMapGenerator(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[]
{
"Core",
"CoreUObject",
"Engine",
"InputCore",
});
PrivateDependencyModuleNames.AddRange(new string[]
{
"Slate",
"SlateCore",
});
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
And the final class looks like so:
#pragma once
#include "CoreMinimal.h"
#include "Components/Slider.h"
#include "Slider2.generated.h"
/**
*
*/
UCLASS()
class RTSMAPGENERATOR_API USlider2 : public USlider
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void set_value(float in_value, bool trigger_event = true);
};
#include "Slider2.h"
#include "Widgets/Input/SSlider.h"
void USlider2::set_value(float in_value, bool trigger_event)
{
if (trigger_event)
{
USlider::SetValue(in_value);
return;
}
if (MySlider.IsValid())
MySlider->SetValue(TAttribute<float>(in_value));
if (Value != in_value)
Value = in_value;
}
I still get the depreciated warnings, so fungers crossed this doesn’t break on next UE update.
I tested the code I gave you before posting. It linked perfectly fine. I see that you figured out to add the modules to your build.cs file. And yes, you will get a warning because your method IS the setter method. You’re defining a new one. That’s why you’re getting the warning.
Hopefully, if it does change in an update, they’ll just move the value to be protected. I can’t see them moving it private when MySlider is protected.
So is the reality here that this can’t be done in blueprints with the current default Slider widget?
You asked for a specific method, so I showed one way to do that. As for your question if there are alternatives? Sure.
If you want to use the original widget directly with the new method, it’s possible. But only if you put your new method directly in the original Slider widget class and rebuild the engine. You’d have a custom build though, but it would work. You’d have to copy it every time you update the engine and you’d be forced to build from source.
As to a solution in blueprint only using the original widget? Dunno. I can’t think of anything. Setting the value is easy. But you need access to MySlider and that’s protected. It’s not even a property. So that’s a no go.
You can always create a custom widget though. Drop a slider in it and pass through whatever you need and duplicate the properties you use. Then you can create your own dispatchers and you can control exactly when they are called. It’s basically just creating a new widget. It’s not the original. But it is viable and gives you the control you need. And it’s BP only. I have no idea what your requirements are, so you’ll have to decide if this is usable or not.
The only other alternative aside from that you said you couldn’t do. I didn’t quite understand the explanation. Unbinding and rebinding OnValueChanged is a valid alternative implementation to your new method in C++ though. I think I like what you have now better though. I don’t like messing with delegates if I don’t have to. But if they do change “Value” to be private, you can use this implementation to keep it working.