View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0002656 | NoesisGUI | C++ SDK | public | 2023-07-31 03:32 | 2023-08-01 11:41 |
Reporter | nikobarli | Assigned To | sfernandez | ||
Priority | normal | Severity | block | Reproducibility | always |
Status | resolved | Resolution | open | ||
Product Version | 3.1 | ||||
Target Version | 3.2.2 | Fixed in Version | 3.2.2 | ||
Summary | 0002656: MenuItem's IsChecked binding is not working properly | ||||
Description | It seems that the binding for MenuItem's IsChecked property to a VM's property doesn't propagate properly. Please apply the attached patch and build the Buttons and Buttons-blend to reproduce the issue. We modify the above Sample to show three controls: Button, CheckBox, and MenuItem that bind to the same IsChecked property. When run as WPF (Buttons-blend), any changes in one of the control will be synced to the other controls, as expected. But when run as NoesisGUI (Buttons) - Clicking MenuItem and changing its IsChecked property is not propagated to other controls - Clicking Button or Checkbox and changing their IsChecked property is initially propagated to MenuItem. However, once the MenuItem is clicked, the propagation cease forever. Please also check the movie we attached. We are running NoesisGUI 3.1.3. Currently it is a blocking issue for us, and we would be very grateful if you can provide solution for this ASAP (including temporary patch). Thanks in advance. | ||||
Steps To Reproduce | Please see the above description | ||||
Tags | No tags attached. | ||||
Platform | Windows | ||||
Test_MenuItem (1).patch (20,205 bytes)
Index: Native/Src/Packages/Samples/Buttons/Data/MainWindow.xaml =================================================================== --- Native/Src/Packages/Samples/Buttons/Data/MainWindow.xaml (revision 166697) +++ Native/Src/Packages/Samples/Buttons/Data/MainWindow.xaml (working copy) @@ -6,170 +6,25 @@ xmlns:local="clr-namespace:Buttons" xmlns:noesis="clr-namespace:NoesisGUIExtensions;assembly=Noesis.GUI.Extensions" Title="NoesisGUI - Buttons" + Background="Gray" d:DesignWidth="1280" d:DesignHeight="720"> - - <Window.Resources> - <Storyboard x:Key="Intro"> - <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="StartButton"> - <EasingDoubleKeyFrame KeyTime="0" Value="0"/> - <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="0"/> - <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="1"> - <EasingDoubleKeyFrame.EasingFunction> - <BackEase EasingMode="EaseOut" Amplitude="0.5"/> - </EasingDoubleKeyFrame.EasingFunction> - </EasingDoubleKeyFrame> - </DoubleAnimationUsingKeyFrames> - <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" Storyboard.TargetName="StartButton"> - <EasingDoubleKeyFrame KeyTime="0" Value="-200"/> - <EasingDoubleKeyFrame KeyTime="0:0:0.3" Value="-200"/> - <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="0"> - <EasingDoubleKeyFrame.EasingFunction> - <CircleEase EasingMode="EaseOut"/> - </EasingDoubleKeyFrame.EasingFunction> - </EasingDoubleKeyFrame> - </DoubleAnimationUsingKeyFrames> - <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="SettingsButton"> - <EasingDoubleKeyFrame KeyTime="0" Value="0"/> - <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0"/> - <EasingDoubleKeyFrame KeyTime="0:0:0.8" Value="1"> - <EasingDoubleKeyFrame.EasingFunction> - <BackEase EasingMode="EaseOut" Amplitude="0.5"/> - </EasingDoubleKeyFrame.EasingFunction> - </EasingDoubleKeyFrame> - </DoubleAnimationUsingKeyFrames> - <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" Storyboard.TargetName="SettingsButton"> - <EasingDoubleKeyFrame KeyTime="0" Value="-200"/> - <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="-200"/> - <EasingDoubleKeyFrame KeyTime="0:0:0.9" Value="0"> - <EasingDoubleKeyFrame.EasingFunction> - <CircleEase EasingMode="EaseOut"/> - </EasingDoubleKeyFrame.EasingFunction> - </EasingDoubleKeyFrame> - </DoubleAnimationUsingKeyFrames> - <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" Storyboard.TargetName="ExitButton"> - <EasingDoubleKeyFrame KeyTime="0" Value="0"/> - <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/> - <EasingDoubleKeyFrame KeyTime="0:0:0.9" Value="1"> - <EasingDoubleKeyFrame.EasingFunction> - <BackEase EasingMode="EaseOut" Amplitude="0.5"/> - </EasingDoubleKeyFrame.EasingFunction> - </EasingDoubleKeyFrame> - </DoubleAnimationUsingKeyFrames> - <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" Storyboard.TargetName="ExitButton"> - <EasingDoubleKeyFrame KeyTime="0" Value="-200"/> - <EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="-200"/> - <EasingDoubleKeyFrame KeyTime="0:0:1" Value="0"> - <EasingDoubleKeyFrame.EasingFunction> - <CircleEase EasingMode="EaseOut"/> - </EasingDoubleKeyFrame.EasingFunction> - </EasingDoubleKeyFrame> - </DoubleAnimationUsingKeyFrames> - </Storyboard> - </Window.Resources> - <Window.Triggers> - <EventTrigger RoutedEvent="FrameworkElement.Loaded"> - <BeginStoryboard Storyboard="{StaticResource Intro}"/> - </EventTrigger> - </Window.Triggers> - <Grid x:Name="LayoutRoot" Background="{StaticResource NoesisLogoBg}"> - <Grid.RowDefinitions> - <RowDefinition Height="80*"/> - <RowDefinition Height="800*"/> - <RowDefinition Height="120*"/> - </Grid.RowDefinitions> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="40*"/> - <ColumnDefinition Width="900*"/> - <ColumnDefinition Width="60*"/> - </Grid.ColumnDefinitions> - <Grid Grid.Column="1" Grid.Row="1"> - <Grid.RowDefinitions> - <RowDefinition Height="200*"/> - <RowDefinition Height="800*"/> - </Grid.RowDefinitions> - <Viewbox x:Name="Logo" Stretch="Uniform"> - <StackPanel Orientation="Horizontal"> - <Path Data="{StaticResource NoesisLogoGeometry}" Fill="{StaticResource NoesisLogoBlueBg}" Stretch="Uniform"/> - <Path Data="{StaticResource NoesisTextGeometry}" Fill="White" Stretch="Uniform" Margin="40,0,0,0" VerticalAlignment="Center"/> - <Path Data="{StaticResource NoesisGuiTextGeometry}" Fill="{StaticResource NoesisLogoBlueBg}" Stretch="Uniform" Margin="40,0,0,0" VerticalAlignment="Center"/> - </StackPanel> - </Viewbox> - <Viewbox Grid.Row="1" Stretch="Uniform"> - <Grid Height="460" Width="768"> - <Grid.RowDefinitions> - <RowDefinition Height="100*"/> - <RowDefinition Height="100*"/> - <RowDefinition Height="30*"/> - <RowDefinition Height="100*"/> - <RowDefinition Height="30*"/> - <RowDefinition Height="100*"/> - </Grid.RowDefinitions> - <Button x:Name="StartButton" Grid.Row="1" Content="START" Margin="60,0,0,0" Command="{Binding StartCommand}" RenderTransformOrigin="0.5,0.5"> - <b:Interaction.Triggers> - <b:EventTrigger EventName="MouseEnter"> - <noesis:SetFocusAction/> - </b:EventTrigger> - <b:EventTrigger EventName="GotFocus"> - <b:PlaySoundAction Source="AudioSlide.mp3" Volume="0.2"/> - </b:EventTrigger> - <b:EventTrigger EventName="Click"> - <b:PlaySoundAction Source="AudioClick.mp3" Volume="0.3"/> - </b:EventTrigger> - </b:Interaction.Triggers> - <Button.RenderTransform> - <TransformGroup> - <ScaleTransform/> - <SkewTransform/> - <RotateTransform/> - <TranslateTransform/> - </TransformGroup> - </Button.RenderTransform> - </Button> - <Button x:Name="SettingsButton" Grid.Row="3" Content="SETTINGS" Margin="60,0,0,0" Command="{Binding SettingsCommand}" RenderTransformOrigin="0.5,0.5"> - <b:Interaction.Triggers> - <b:EventTrigger EventName="MouseEnter"> - <noesis:SetFocusAction/> - </b:EventTrigger> - <b:EventTrigger EventName="GotFocus"> - <b:PlaySoundAction Source="AudioSlide.mp3" Volume="0.2"/> - </b:EventTrigger> - <b:EventTrigger EventName="Click"> - <b:PlaySoundAction Source="AudioClick.mp3" Volume="0.3"/> - </b:EventTrigger> - </b:Interaction.Triggers> - <Button.RenderTransform> - <TransformGroup> - <ScaleTransform/> - <SkewTransform/> - <RotateTransform/> - <TranslateTransform/> - </TransformGroup> - </Button.RenderTransform> - </Button> - <Button x:Name="ExitButton" Grid.Row="5" Content="EXIT" Margin="60,0,0,0" Command="{Binding ExitCommand}" RenderTransformOrigin="0.5,0.5"> - <b:Interaction.Triggers> - <b:EventTrigger EventName="MouseEnter"> - <noesis:SetFocusAction/> - </b:EventTrigger> - <b:EventTrigger EventName="GotFocus"> - <b:PlaySoundAction Source="AudioSlide.mp3" Volume="0.2"/> - </b:EventTrigger> - <b:EventTrigger EventName="Click"> - <b:PlaySoundAction Source="AudioClick.mp3" Volume="0.3"/> - </b:EventTrigger> - </b:Interaction.Triggers> - <Button.RenderTransform> - <TransformGroup> - <ScaleTransform/> - <SkewTransform/> - <RotateTransform/> - <TranslateTransform/> - </TransformGroup> - </Button.RenderTransform> - </Button> - </Grid> - </Viewbox> - </Grid> - </Grid> - + <Window.Resources> + <ResourceDictionary Source="pack://application:,,,/Noesis.GUI.Extensions;component/Theme/NoesisTheme.DarkBlue.xaml" /> + </Window.Resources> + <StackPanel> + <StackPanel Orientation="Horizontal"> + <StackPanel Orientation="Horizontal"> + <ToggleButton Content="IsChecked_1" IsChecked="{Binding IsChecked_1}" Command="{Binding Command_1}" Width="150" Margin="5"/> + <ToggleButton Content="IsChecked_1" IsChecked="{Binding IsChecked_2}" Command="{Binding Command_2}" Width="150" Margin="5"/> + </StackPanel> + </StackPanel> + <StackPanel Orientation="Horizontal"> + <CheckBox Content="CheckBox_1" IsChecked="{Binding IsChecked_1}" Width="150" Margin="5"/> + <CheckBox Content="CheckBox_2" IsChecked="{Binding IsChecked_2}" Width="150" Margin="5"/> + </StackPanel> + <StackPanel Orientation="Horizontal"> + <MenuItem Header="MenuItem_1" IsChecked="{Binding IsChecked_1}" IsCheckable="True" Width="150" Margin="5"/> + <MenuItem Header="MenuItem_2" IsChecked="{Binding IsChecked_2}" IsCheckable="True" Width="150" Margin="5"/> + </StackPanel> + </StackPanel> </Window> \ No newline at end of file Index: Native/Src/Packages/Samples/Buttons/Data/ViewModel.cs =================================================================== --- Native/Src/Packages/Samples/Buttons/Data/ViewModel.cs (revision 166697) +++ Native/Src/Packages/Samples/Buttons/Data/ViewModel.cs (working copy) @@ -6,49 +6,67 @@ using System; #endif +using System.ComponentModel; +using System.Runtime.CompilerServices; + namespace Buttons { /// <summary> /// Buttons sample view model /// </summary> - public class ViewModel + public class ViewModel : INotifyPropertyChanged { public ViewModel() { - StartCommand = new DelegateCommand(this.Start); - SettingsCommand = new DelegateCommand(this.Settings); - ExitCommand = new DelegateCommand(this.Exit); + Command_1 = new DelegateCommand((o) => { + IsChecked_1 = true; + IsChecked_2 = false; + }); + + Command_2 = new DelegateCommand((o) => { + IsChecked_1 = false; + IsChecked_2 = true; + }); + } - public DelegateCommand StartCommand { get; private set; } - public DelegateCommand SettingsCommand { get; private set; } - public DelegateCommand ExitCommand { get; private set; } - - private void Start(object parameter) + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { - #if NOESIS - Debug.Log("Start Game"); - #else - Console.WriteLine("Start Game"); - #endif + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - private void Settings(object parameter) + bool m_isChecked_1 = true; + public bool IsChecked_1 { - #if NOESIS - Debug.Log("Change Settings"); - #else - Console.WriteLine("Change Settings"); - #endif + get + { + return m_isChecked_1; + } + set + { + m_isChecked_1 = value; + OnPropertyChanged("IsChecked_1"); + } } - private void Exit(object parameter) + bool m_isChecked_2 = false; + public bool IsChecked_2 { - #if NOESIS - Debug.Log("Exit Game"); - #else - Console.WriteLine("Exit Game"); - #endif + get + { + return m_isChecked_2; + } + set + { + m_isChecked_2 = value; + OnPropertyChanged("IsChecked_2"); + } } + + public DelegateCommand Command_1 { get; set; } + public DelegateCommand Command_2 { get; set; } + } } Index: Native/Src/Packages/Samples/Buttons/Src/ViewModel.cpp =================================================================== --- Native/Src/Packages/Samples/Buttons/Src/ViewModel.cpp (revision 166697) +++ Native/Src/Packages/Samples/Buttons/Src/ViewModel.cpp (working copy) @@ -12,60 +12,48 @@ using namespace NoesisApp; using namespace Buttons; +using namespace Noesis; - //////////////////////////////////////////////////////////////////////////////////////////////////// ViewModel::ViewModel() { - _startCommand.SetExecuteFunc(MakeDelegate(this, &ViewModel::Start)); - _settingsCommand.SetExecuteFunc(MakeDelegate(this, &ViewModel::Settings)); - _exitCommand.SetExecuteFunc(MakeDelegate(this, &ViewModel::Exit)); + m_command_1.SetExecuteFunc(MakeDelegate(this, &ViewModel::Check_1)); + m_command_2.SetExecuteFunc(MakeDelegate(this, &ViewModel::Check_2)); } -//////////////////////////////////////////////////////////////////////////////////////////////////// -const DelegateCommand* ViewModel::GetStartCommand() const +void ViewModel::SetIsChecked_1(bool b) { - return &_startCommand; + m_isChecked_1 = b; + OnPropertyChanged("IsChecked_1"); } -//////////////////////////////////////////////////////////////////////////////////////////////////// -const DelegateCommand* ViewModel::GetSettingsCommand() const +void ViewModel::SetIsChecked_2(bool b) { - return &_settingsCommand; + m_isChecked_2 = b; + OnPropertyChanged("IsChecked_2"); } -//////////////////////////////////////////////////////////////////////////////////////////////////// -const DelegateCommand* ViewModel::GetExitCommand() const +void ViewModel::Check_1(Noesis::BaseComponent*) { - return &_exitCommand; + SetIsChecked_1(true); + SetIsChecked_2(false); } -//////////////////////////////////////////////////////////////////////////////////////////////////// -void ViewModel::Start(BaseComponent*) +void ViewModel::Check_2(Noesis::BaseComponent*) { - NS_LOG_INFO("Start Game"); + SetIsChecked_1(false); + SetIsChecked_2(true); } //////////////////////////////////////////////////////////////////////////////////////////////////// -void ViewModel::Settings(BaseComponent*) -{ - NS_LOG_INFO("Change Settings"); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void ViewModel::Exit(BaseComponent*) -{ - NS_LOG_INFO("Exit Game"); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// NS_BEGIN_COLD_REGION NS_IMPLEMENT_REFLECTION(ViewModel) { - NsProp("StartCommand", &ViewModel::GetStartCommand); - NsProp("SettingsCommand", &ViewModel::GetSettingsCommand); - NsProp("ExitCommand", &ViewModel::GetExitCommand); + NsProp("IsChecked_1", &GetIsChecked_1, &SetIsChecked_1); + NsProp("IsChecked_2", &GetIsChecked_2, &SetIsChecked_2); + NsProp("Command_1", &GetCommand_1); + NsProp("Command_2", &GetCommand_2); } NS_END_COLD_REGION Index: Native/Src/Packages/Samples/Buttons/Src/ViewModel.h =================================================================== --- Native/Src/Packages/Samples/Buttons/Src/ViewModel.h (revision 166697) +++ Native/Src/Packages/Samples/Buttons/Src/ViewModel.h (working copy) @@ -12,32 +12,39 @@ #include <NsCore/ReflectionDeclare.h> #include <NsCore/BaseComponent.h> #include <NsApp/DelegateCommand.h> +#include <NsGui/INotifyPropertyChanged.h> +#include <NsApp/NotifyPropertyChangedBase.h> - namespace Buttons { //////////////////////////////////////////////////////////////////////////////////////////////////// -class ViewModel final: public Noesis::BaseComponent +class ViewModel final: public NoesisApp::NotifyPropertyChangedBase { public: ViewModel(); private: - const NoesisApp::DelegateCommand* GetStartCommand() const; - const NoesisApp::DelegateCommand* GetSettingsCommand() const; - const NoesisApp::DelegateCommand* GetExitCommand() const; + bool GetIsChecked_1() const { return m_isChecked_1; } + bool GetIsChecked_2() const { return m_isChecked_2; } - void Start(BaseComponent* param); - void Settings(BaseComponent* param); - void Exit(BaseComponent* param); + const NoesisApp::DelegateCommand* GetCommand_1() const { return &m_command_1; } + const NoesisApp::DelegateCommand* GetCommand_2() const { return &m_command_2; } + void SetIsChecked_1(bool b); + void SetIsChecked_2(bool b); + + void Check_1(Noesis::BaseComponent* param); + void Check_2(Noesis::BaseComponent* param); + private: - NoesisApp::DelegateCommand _startCommand; - NoesisApp::DelegateCommand _settingsCommand; - NoesisApp::DelegateCommand _exitCommand; + bool m_isChecked_1 = true; + bool m_isChecked_2 = false; - NS_DECLARE_REFLECTION(ViewModel, BaseComponent) + NoesisApp::DelegateCommand m_command_1; + NoesisApp::DelegateCommand m_command_2; + + NS_DECLARE_REFLECTION(ViewModel, NotifyPropertyChangedBase) }; } |
|
Hi Niko, it seems the MenuItem.IsChecked property was missing the BindsTwoWayByDefault attribute. Could you please test the following patch: Index: MenuItem.cpp =================================================================== --- MenuItem.cpp (revision 12600) +++ MenuItem.cpp (working copy) @@ -1123,13 +1123,15 @@ data->RegisterProperty<bool>(IsCheckableProperty, "IsCheckable", FrameworkPropertyMetadata::Create(false)); data->RegisterProperty<bool>(IsCheckedProperty, "IsChecked", - FrameworkPropertyMetadata::Create(false)); + FrameworkPropertyMetadata::Create(false, + FrameworkPropertyMetadataOptions_BindsTwoWayByDefault)); data->RegisterPropertyRO<bool>(IsHighlightedProperty, "IsHighlighted", FrameworkPropertyMetadata::Create(false)); data->RegisterPropertyRO<bool>(IsPressedProperty, "IsPressed", FrameworkPropertyMetadata::Create(false)); data->RegisterProperty<bool>(IsSubmenuOpenProperty, "IsSubmenuOpen", - FrameworkPropertyMetadata::Create(false)); + FrameworkPropertyMetadata::Create(false, + FrameworkPropertyMetadataOptions_BindsTwoWayByDefault)); data->RegisterPropertyRO<MenuItemRole>(RoleProperty, "Role", FrameworkPropertyMetadata::Create(MenuItemRole_TopLevelItem)); data->RegisterProperty<bool>(StaysOpenOnClickProperty, "StaysOpenOnClick", |
|
Hi Sergio, Thanks for the very quick support ! It seems to work. Now I am giving it to the team here to be applied to our codes and tested more rigorously. I will let you know again the result. |
|
Hi Sergio, The team said the fix is OK. Thank you ! |
|
Thanks for the confirmation! Fixed in changeset r12640 |
|
Date Modified | Username | Field | Change |
---|---|---|---|
2023-07-31 03:32 | nikobarli | New Issue | |
2023-07-31 03:32 | nikobarli | File Added: Test_MenuItem (1).patch | |
2023-07-31 03:32 | nikobarli | File Added: Test_MenuItem.mp4 | |
2023-07-31 03:39 | nikobarli | Description Updated | |
2023-07-31 11:33 | jsantos | Assigned To | => sfernandez |
2023-07-31 11:33 | jsantos | Status | new => assigned |
2023-07-31 13:52 | sfernandez | Status | assigned => feedback |
2023-07-31 13:52 | sfernandez | Note Added: 0008613 | |
2023-07-31 14:46 | nikobarli | Note Added: 0008614 | |
2023-07-31 14:46 | nikobarli | Status | feedback => assigned |
2023-08-01 06:14 | nikobarli | Note Added: 0008616 | |
2023-08-01 11:41 | sfernandez | Status | assigned => resolved |
2023-08-01 11:41 | sfernandez | Fixed in Version | => 3.2.2 |
2023-08-01 11:41 | sfernandez | Target Version | => 3.2.2 |
2023-08-01 11:41 | sfernandez | Note Added: 0008617 | |
2023-08-01 11:41 | sfernandez | Note Edited: 0008617 |