maui icon indicating copy to clipboard operation
maui copied to clipboard

Button IsEnabled property in Windows Platform

Open jcmontoya opened this issue 2 years ago • 25 comments

Description

When I set up the Button's "IsEnabled" property, programmatically. The expected behavior is working in all platforms. However, the formatting of the button in the Windows Platform gets lost after a couple of changes. In the Android platform it works as expected.

Steps to Reproduce

  1. Create a new Maui App
  2. Add a Grid and a StackLayout with two buttons
  3. Disable of the buttons by setting IsEnabled="False"
  4. Add code to programmatically change the is enabled property.
    <Grid  BackgroundColor="Black">
          <StackLayout  Orientation="Horizontal" HorizontalOptions="Center">
            <Button x:Name="btn1" Text="Start" WidthRequest="100" Background="Green" Clicked="btn1_Clicked"></Button>
            <Button x:Name="btn2" Text="Stop" WidthRequest="100" Background="Green" Clicked="btn2_Clicked" IsEnabled="False"></Button>
        </StackLayout>
    </Grid>
   private void btn1_Clicked(object sender, EventArgs e)
    {
        btn1.IsEnabled = false;
        btn2.IsEnabled = true;
    }

    private void btn2_Clicked(object sender, EventArgs e)
    {
        btn1.IsEnabled = true;
        btn2.IsEnabled = false;
    }

5.Click on the buttons a couple of times to enable and disable them. After the first cycle both buttons will look disabled. the functionality will still be working but it is confusing for the user. This only happens in windows, not in Android or IOS.

Version with bug

Release Candidate 3 (current)

Last version that worked well

Release Candidate 3 (current)

Affected platforms

Windows

Affected platform versions

Windows 11

Did you find any workaround?

No

Relevant log output

No response

jcmontoya avatar May 20 '22 17:05 jcmontoya

verified repro on windows. repro project: MauiApp36.zip

kristinx0211 avatar May 23 '22 06:05 kristinx0211

I am also experiencing this same issue on Windows builds. This appears to be happening on Windows 10 in addition to Windows 11 (as reported by @jcmontoya).

wbaldoumas avatar Jun 01 '22 01:06 wbaldoumas

this also never really worked well in XF, so there I used VisualStateManager to change the IsEnabled property and here the change always worked. But in MAUI VisualStateManager is broken (#8003), so I can't use it.

Has anyone found a working way to change button states correctly?

MagicAndre1981 avatar Jul 11 '22 07:07 MagicAndre1981

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

ghost avatar Aug 30 '22 14:08 ghost

Need fix the issue asap, it also affects Android platform

LuoyeAn avatar Sep 07 '22 06:09 LuoyeAn

When is this issue going to be addressed? This issue is now 5 months, is a major piece of broken functionality on Windows, no known workaround, no official response, and sits unassigned and with no milestone. For all practical purposes this is looks like a WNF, the silence is speaking volumes.

ascmt avatar Oct 12 '22 15:10 ascmt

issue-7377 Could you try again with the latest version (net7 rc2)?. Cannot reproduce the issue.

jsuarezruiz avatar Oct 19 '22 09:10 jsuarezruiz

Hi @jcmontoya. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

ghost avatar Oct 19 '22 09:10 ghost

@jsuarezruiz Does your last comment imply that this issue will NOT ever be addressed in net6?

ascmt avatar Oct 20 '22 14:10 ascmt

Seems the issue happens if the button is re enabled after an await method.

I'm attaching a screenshot of my code snippet for reference.

The IsEnabled = True works as intended if there was no await call between setting it false and true. But if there is an await call before resetting it to true, the visual element does not reset.

Image Reference image

NirabhraDas013 avatar Oct 31 '22 06:10 NirabhraDas013

I also have the same issue with .NET 6, attaching a screenshot. _grid.Children is just a typed version of the Grid.Children, I use _grid.Children.Select(x => (Button)x).

image

IsVisible works though.

Catherine25 avatar Nov 01 '22 23:11 Catherine25

I encountered the same issue with .NET 7. A repo for reproduction: ButtonIssueSampleNet7.zip

Capture: https://user-images.githubusercontent.com/25539067/200715698-6048860a-cd40-4849-a77c-2dab88de872d.mp4

shizukudrops avatar Nov 09 '22 01:11 shizukudrops

I tried fiddling around to find a (somewhat stupid) workaround, as in actually having two buttons, one permanently enabled and one permanently not, and binding both their IsVisible properties to the same boolean with an inverse converter.

GitHub

The correct buttons are displayed, yet the button with the enabled status fixed at "True" STILL grays itself out when it appears. It seems something is forcing the IsEnabled property to false, perhaps with these await statements as mentioned by @NirabhraDas013.

Not quite to be honest. The button itself is not disabled. The click function works as intended but the visuals are stuck at disabled status.

Edit : I meant "it seems something is forcing the BackgroundColor property to match an "IsEnabled = false" state. The button IsEnabled status in itself is working correctly."

The (very ugly) workaround that does work however is to make the button color transparent and place a boxview right behind your button, with a binded Color property. If it's stupid but it works...

stupidworkaround

So writing a custom control "CustomButton" composed of a boxview and a button could be a somewhat decent workaround ?

5CoJL avatar Nov 10 '22 13:11 5CoJL

I can confirm the same issue ~~and the analysis of await vs not seems to be spot on.~~

borrrden avatar Nov 10 '22 13:11 borrrden

@5CoJL --

It seems something is forcing the IsEnabled property to false

Not quite to be honest. The button itself is not disabled. The click function works as intended but the visuals are stuck at disabled status.

The (very ugly) workaround that does work however is to make the button color transparent and place a BoxView right behind your button, with a binded Color property. If it's stupid but it works...

I ended up doing something similar. but maybe a bit more complicated than it needed to be (being new to . NET doesn't help haha). I positioned a Label with a border to set it look like the button and turned it on and fixed Text and grayed background. This wat the Button is never disabled just hidden from view

@borrrden -- I'm not a .NET developer by trade. I'm usually Unity Developer. I've tried my hands at Avalonia and now trying MAUI as we need a cross platform desktop app. I might have missed something in the await analysis. But I have tried multiple times with new projects and the behavior before and after the await call remains same for me. Could you share your findings please so I can try to look from a different angle

NirabhraDas013 avatar Nov 10 '22 13:11 NirabhraDas013

@Catherine25 -- Have you checked if the button click works after the IsEnabled = true call? For me only the visual was stuck at the disabled state but the button click still worked so the button itself had been enabled

NirabhraDas013 avatar Nov 10 '22 13:11 NirabhraDas013

@NirabhraDas013,

I just double checked. For me the click works only when another button was pressed after mine. Clicking anything else and minimizing the window will not make the same effect.

So, 2+ times clicking on A button will still raise Click event only 1 time. When I click for example A -> B -> A, Click event might be raised 2+ times.

Catherine25 avatar Nov 10 '22 17:11 Catherine25

@NirabhraDas013 I take it back, It's not completely that cut and dry. Here is the situation in my application:

I have two buttons (say, A and B) and an Entry field. At the start A is enabled, and B is disabled. They are bound to the same backing variable, just one is run through an InvertedBoolConverter.

  1. Pushing A sets the backing variable to true, which sets A to disabled and B to enabled, and then performs an await. The visual states are as expected (A gray B blue)
  2. Changing the text of the entry field resets the backing variable to false, which sets A to enabled and B to disabled. This results in both buttons being in the disabled visual state (gray) which is wrong.
  3. Repeating 1 puts the buttons back into the correct state (A gray B blue)

borrrden avatar Nov 10 '22 23:11 borrrden

I use VisualStateManager to change the change the state via VisualStateManager.GoToState(ButtonLog1l, "Disabled");. My mistake was to not include <VisualStateGroupList> entry now everything works.

<Button x:Name="Button1" Text = "Foo" Clicked="OnButton1Clicked">
<VisualStateManager.VisualStateGroups>
	<VisualStateGroupList>
		<VisualStateGroup Name="ValidityStates">
			<VisualState Name="Enabled" />
			<VisualState Name="Disabled">
				<VisualState.Setters>
					<Setter Property="IsEnabled" Value="False" />
				</VisualState.Setters>
			</VisualState>
		</VisualStateGroup>
	</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
</Button>

MagicAndre1981 avatar Nov 11 '22 06:11 MagicAndre1981

IsEnabled works for me if I never ever modify it synchronously from a Clicked event.

Just awaiting anything does not guarantee that the rest of the method runs asynchronously. I must await Task.Yield to be sure.

This is only necessary on Windows. When running on Android, IsEnabled works as expected.

private async void OnMyButtonClicked(object sender, EventArgs e) { await Task.Yield(); // without this line all future calls (in all methods) to buttonDisconnect.IsEnabled will not work buttonDisconnect.IsEnabled = false; // the rest of your code here }

tomas-andersson avatar Nov 11 '22 11:11 tomas-andersson

@tomas-andersson await Task.Yield() works for me! Thank you very much!

shizukudrops avatar Nov 11 '22 11:11 shizukudrops

Did anyone find a workaround for commands?

    [RelayCommand(CanExecute = nameof(CanExecute))]
    private async Task Second()
    {
        await Task.Yield();
        CanExecute = false;
        await Task.Delay(3000);
        CanExecute = true;
    }

Is still not working for me. The binded button is stuck in the greyed-out state, even though it becomes clickable after the 3s delay.

leojablon1 avatar Nov 17 '22 13:11 leojablon1

Today updated my project to .net 7 (minimum target windows framework 10.0.22000.0) Issue is still there.

Catherine25 avatar Nov 22 '22 23:11 Catherine25

Today updated my project to .net 7 (minimum target windows framework 10.0.22000.0) Issue is still there.

use VisualStateManager, this works fine for me and also worked in XF, but in MAUI compared toXF I need to add <VisualStateGroupList>

MagicAndre1981 avatar Nov 23 '22 07:11 MagicAndre1981

I really don't want to be writing all the boilerplate that comes with visual states just to convey an enabled/disabled button.

chaddoncooper avatar Nov 24 '22 11:11 chaddoncooper

I found a workaround: using the command interface. https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/button?view=net-maui-7.0#use-the-command-interface Change button's state via function "canExecute", works for me.

vaporz avatar Nov 28 '22 08:11 vaporz

I really don't want to be writing all the boilerplate that comes with visual states just to convey an enabled/disabled button.

in XF I also sometime got issues enabling/disabling buttons and here VisualStateManager always worked. So you should use the proper solution VisualStateManager

MagicAndre1981 avatar Nov 29 '22 09:11 MagicAndre1981

Morning guys, any update on this? Still happens on VS 2022 17.4.2 and .NET 7.0.100

danielancines avatar Dec 07 '22 11:12 danielancines

If it helps, I noticed a weird addition to this issue. Firstly, yes the issue seems to only repro if an async method that actually does something is called (for example, await DisplayAlert(...)). So, for example, we start with IsEnabled on the button true:

image

if IsEnabled is set false then the button shows disabled. Then the DisplayAlert is called. You will see that the button is, as expected, disabled.

image

We click 'OK' on the alert, then IsEnabled is set true. Now it stays in the visual state for disabled, even though it is actually enabled as it is clickable again:

image

The weird addition is that if I simply copy-paste to put TWO buttons on the page, then the one I don't click works as expected. However, the one I clicked stays in the disabled state. See example below. Firstly, before we click the first button:

image

Next we click the first button. Note that BOTH buttons disable, as they should (I am binding to an enabled property):

image

We click 'OK' and the alert closes... but the button I clicked stays disabled. The one I did NOT click shows the enabled visual state though!

image

It appears that it's something to do with the actual click handling on windows somehow overriding the visual state callback once it's done, or something related to that.

dan-matthews avatar Dec 27 '22 11:12 dan-matthews

The initially reported issue and the version of the issue with async/await are fixed by #11840. The fix should be in the next service release.

hartez avatar Dec 27 '22 18:12 hartez