I'm trying to bind an int to a ComboBox.SelectedIndex. Binding to a TextBox is trivial, but it appears that some extra plumbing is needed for the SelectedIndex. Here's the repro:
<!--MainWindow.xaml-->
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="MiniEditor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MiniEditor"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MiniEditor">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left" VerticalAlignment="Top">
<ListView x:Name="MEList" Background="LightGoldenrodYellow" CanReorderItems="True" Height="600">
<ListView.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="#" HorizontalAlignment="Center"/>
<TextBlock Grid.Column="1" Text="Name" HorizontalAlignment="Center"/>
<TextBlock Grid.Column="2" Text="Op" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:OpDatWRC">
<Grid x:Name="MEGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind Path=opnum}" Width="30" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Grid.Column="1" Text="{x:Bind Path=name}"/>
<!--<ComboBox Grid.Column="2" Width="80" SelectedIndex="{x:Bind Path=op, Mode=TwoWay}">-->
<ComboBox Grid.Column="2" Width="80" SelectedIndex="{x:Bind Path=op, Mode=OneTime}">
<x:String><</x:String>
<x:String>></x:String>
<x:String>=</x:String>
<x:String>|<|</x:String>
<x:String>|>|</x:String>
<x:String>|=|</x:String>
</ComboBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
//MainWindow.idl
namespace MiniEditor
{
struct OpDatWRC
{
Int32 opnum;
String name;
Int32 op;
};
[default_interface]
runtimeclass MainWindow : Microsoft.UI.Xaml.Window
{
MainWindow();
void AddOp(String name, Int32 op);
Windows.Foundation.Collections.IVectorView<OpDatWRC> GetOps();
}
}
//MainWindow.xaml.h
#pragma once
#include "MainWindow.g.h"
namespace winrt::MiniEditor::implementation
{
struct MainWindow : MainWindowT<MainWindow>
{
MainWindow();
void AddOp(hstring const& name, int32_t op);
winrt::Windows::Foundation::Collections::IVectorView<winrt::MiniEditor::OpDatWRC> GetOps();
private:
winrt::Windows::Foundation::Collections::IObservableVector<winrt::MiniEditor::OpDatWRC> ops{ winrt::single_threaded_observable_vector<winrt::MiniEditor::OpDatWRC>() };
};
}
namespace winrt::MiniEditor::factory_implementation
{
struct MainWindow : MainWindowT<MainWindow, implementation::MainWindow>
{
};
}
//MainWindow.xaml.cpp
#include "pch.h"
#include "MainWindow.xaml.h"
#if __has_include("MainWindow.g.cpp")
#include "MainWindow.g.cpp"
#endif
using namespace winrt;
using namespace Microsoft::UI::Xaml;
namespace winrt::MiniEditor::implementation
{
MainWindow::MainWindow()
{
InitializeComponent();
AddOp(L"One", 0);
AddOp(L"Two", 1);
AddOp(L"Three", 2);
MEList().ItemsSource(ops);
}
void MainWindow::AddOp(hstring const& name, int32_t op)
{
// Create an instance of OpDatWRC
winrt::MiniEditor::OpDatWRC newItem;
newItem.opnum = ops.Size();
newItem.name = name;
newItem.op = op;
// Add the new item to the IObservableVector
ops.Append(newItem);
}
winrt::Windows::Foundation::Collections::IVectorView<winrt::MiniEditor::OpDatWRC> MainWindow::GetOps()
{
return ops.GetView();
}
}
As shown, the binding to the ComboBox.SelectedIndex is "OneTime" and everything compiles and runs. However, when I switch to the desired "TwoWay" binding, I get the following compiler error:
C2064 term does not evaluate to a function taking 1 arguments
The error points to a line in MainWindow.xaml.g.hpp. Here's the code:
case 8: // MainWindow.xaml line 38
{
auto targetElement = target.as<::winrt::Microsoft::UI::Xaml::Controls::ComboBox>();
obj8 = targetElement;
obj8.RegisterPropertyChangedCallback(::winrt::Microsoft::UI::Xaml::Controls::Primitives::Selector::SelectedIndexProperty(),
[weakThis{ this->weak_from_this() }, this] (DependencyObject const& sender, DependencyProperty const& prop)
{
if (auto strongThis{ weakThis.lock() })
{
if (IsInitialized())
{
// Update Two Way binding
GetDataRoot().op(obj8.SelectedIndex()); //<= error is here
}
}
});
}
break;
What do I need to add to my code to fix this error? Do I need to explicitly define a setter for op?
Thanks to @SimonMourier, I have working code. Here's modified portion of the .idl:
runtimeclass OpDatWRC : Microsoft.UI.Xaml.Data.INotifyPropertyChanged
{
OpDatWRC();
Int32 opnum;
String name;
Int32 op;
};
Same for the .h:
// OpDatWRC.h
#pragma once
#include "OpDatWRC.g.h"
namespace winrt::MiniEditor::implementation
{
struct OpDatWRC : OpDatWRCT<OpDatWRC>
{
OpDatWRC() : m_opnum(0), m_name(L"BLANK"), m_op(0) {}
int32_t opnum() { return m_opnum; }
void opnum(int32_t value);
hstring name() { return m_name; }
void name(hstring const& value);
int32_t op() { return m_op; }
void op(int32_t value);
winrt::event_token PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler);
void PropertyChanged(winrt::event_token const& token) noexcept;
private:
int m_opnum;
hstring m_name;
int m_op;
winrt::event<Microsoft::UI::Xaml::Data::PropertyChangedEventHandler> m_propertyChanged;
};
}
namespace winrt::MiniEditor::factory_implementation
{
struct OpDatWRC : OpDatWRCT<OpDatWRC, implementation::OpDatWRC>
{
};
}
and .cpp:
// OpDatWRC.cpp
#include "pch.h"
#include "OpDatWRC.h"
#include "OpDatWRC.g.cpp"
namespace winrt::MiniEditor::implementation
{
void OpDatWRC::opnum(int32_t value)
{
if (m_opnum != value)
{
m_opnum = value;
m_propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Opnum" });
}
}
void OpDatWRC::name(hstring const& value)
{
if (m_name != value)
{
m_name = value;
m_propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Name" });
}
}
void OpDatWRC::op(int32_t value)
{
if (m_op != value)
{
m_op = value;
m_propertyChanged(*this, Microsoft::UI::Xaml::Data::PropertyChangedEventArgs{ L"Op" });
}
}
winrt::event_token OpDatWRC::PropertyChanged(winrt::Microsoft::UI::Xaml::Data::PropertyChangedEventHandler const& handler)
{
return m_propertyChanged.add(handler);
}
void OpDatWRC::PropertyChanged(winrt::event_token const& token) noexcept
{
m_propertyChanged.remove(token);
}
}
Finally, here's the updated XAML:
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="MiniEditor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MiniEditor"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MiniEditor">
<StackPanel Orientation="Vertical" HorizontalAlignment="Left" VerticalAlignment="Top">
<ListView x:Name="MEList" Background="LightGoldenrodYellow" CanReorderItems="True" Height="600">
<ListView.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="#" HorizontalAlignment="Center"/>
<TextBlock Grid.Column="1" Text="Name" HorizontalAlignment="Center"/>
<TextBlock Grid.Column="2" Text="Op" HorizontalAlignment="Center"/>
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:OpDatWRC">
<Grid x:Name="MEGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="80"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{x:Bind Path=opnum}" Width="30" VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Grid.Column="1" Text="{x:Bind Path=name, Mode=TwoWay}"/>
<ComboBox Grid.Column="2" Width="80" SelectedIndex="{x:Bind Path=op, Mode=TwoWay}">
<!--<ComboBox Grid.Column="2" Width="80" SelectedIndex="{x:Bind Path=op, Mode=OneTime}">-->
<x:String><</x:String>
<x:String>></x:String>
<x:String>=</x:String>
<x:String>|<|</x:String>
<x:String>|>|</x:String>
<x:String>|=|</x:String>
</ComboBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
The entire solution is in https://github.com/dr-eck/MiniEditor As a bonus, working code for the WinUI 3 version of the Bookstore example is added. The main trick for that code was changing Windows.UI.Xaml.Data.INotifyPropertyChanged to Microsoft.UI.Xaml.Data.INotifyPropertyChanged.