I'm writing a UWP app and have a few comboboxes bound to my view model. For some reason the comboboxes aren't updating the bound value nor loading it when they render if I set the values manually while debugging. I see that this is a common issue but I can't spot any of the causes I've seen other people have so far. Following is my stripped down code:
XAML:
<Page
x:Class="UWPApp.Scorekeeper.SelectGoalTime"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UWPApp.Scorekeeper"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="MainElement">
<Grid Background="{ThemeResource SystemControlBackgroundAccentBrush}">
<ComboBox x:Name="MinutesSelect" SelectedValue="{Binding ElementName=MainElement,Path=ViewModel.Minutes}" ItemsSource="{Binding ElementName=MainElement,Path=MinutesList}"/>
</Grid>
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using UWPApp.Scorekeeper.Interfaces;
using UWPApp.Scorekeeper.Models.ViewModels;
using UWPApp.Scorekeeper.Models.TransportClasses;
using Windows.UI.Popups;
using UWPApp.Scorekeeper.Models;
using UWPApp.Scorekeeper.Toolbox;
namespace UWPApp.Scorekeeper
{
public sealed partial class SelectGoalTime : Page
{
public AddGoal_FVM ViewModel { get; set; }
public List<int> MinutesList { get; set; } = Enumerable.Range(0,21).ToList();
public SelectGoalTime()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var message = e.Parameter as GoalMessage;
ViewModel = message.ViewModel;
}
}
}
AddGoal_FVM
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace UWPApp.Scorekeeper.Models.ViewModels
{
public class AddGoal_FVM
{
public int Minutes { get; set; }
}
}
Since I don't have the reputation to add a comment, I'll have to share this way:
Found here, https://twitter.com/kdawg02/status/746734845393518592, BUG UWP ComboBox SelectedValue has to be the last property in XAML otherwise it will not set the value on load.
Hope it helps, I had no end of troubles with trying to bind a combobox in UWP with the MVVM pattern.
Related
i created a custom control in WPF for Windows 10 Apps. The problem is that the binding in the main class doesn't work. It binds to my custom control. Can anyone see the problem? How can i repair the code. It doesnt work. Specially the binding doesnt work. How can i solve that. I have no idea, how to fix that.
View Model
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using AppDemo.Annotations;
namespace AppDemo
{
public class ViewModel : INotifyPropertyChanged
{
private String t1, t2;
public String T1
{
get { return t1; }
set
{
t1 = value;
Concat = T1 + T2;
}
}
public String T2
{
get { return t2; }
set
{
t2 = value;
Concat = T1 + T2;
}
}
private String concat;
public String Concat
{
get { return concat; }
set
{
concat = value;
OnPropertyChanged(nameof(Concat));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
<UserControl
x:Class="AppDemo.ExampleControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AppDemo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid Name="grid">
<StackPanel>
<TextBox Text="{Binding T1,Mode=TwoWay}"/>
<TextBox Text="{Binding T2,Mode=TwoWay}"/>
</StackPanel>
</Grid>
</UserControl>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using AppDemo.Annotations;
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
namespace AppDemo
{
public sealed partial class ExampleControl : UserControl
{
public static readonly DependencyProperty ConcatProperty = DependencyProperty.Register(
"Concat", typeof(String), typeof(ExampleControl), new PropertyMetadata(default(String)));
private ViewModel m;
public String Concat
{
get { return (String)GetValue(ConcatProperty); }
set { SetValue(ConcatProperty, value); }
}
public ExampleControl()
{
this.InitializeComponent();
m = new ViewModel();
grid.DataContext = m;
m.PropertyChanged += M_PropertyChanged;
}
private void M_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Concat = m.Concat;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
<Page
x:Class="AppDemo.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:AppDemo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<local:ExampleControl Concat="{Binding C}"/>
<Button Name="btnTest" Click="BtnTest_OnClick">Test</Button>
</StackPanel>
</Grid>
</Page>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace AppDemo
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public String C { get; set; }
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
private void BtnTest_OnClick(object sender, RoutedEventArgs e)
{
String msg = C;
}
}
}
It is generally better to put your properties that you want to bind to, within their own distinct view-model class - not mixed in with your view code. This code is unnecessarily obtuse.
BTW...
Your UserControl named ExampleControl, has textboxes whose Text property is bound to T1 and T2. However, when you set either of those values - they set the Concat property but fail to raise the PropertyChanged event with their own name.
You'd fix that with, for example:
public String T1
{
set
{
t1 = value;
OnPropertyChanged(nameof(T1));
Concat = T1 + T2;
}
}
Set the Mode of the Binding to TwoWay in your MainPage:
<local:ExampleControl Concat="{Binding C, Mode=TwoWay}"/>
i'm trying to learn WPF and on top of that the MVVM style of doing things.
I have a simple practice app in which i would like to display codes in a combo box.
My Code
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SteamCodes
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<Codes> codes;
public MainWindow()
{
InitializeComponent();
codes = new ObservableCollection<Codes>()
{
new Codes() {CodeID = "1", Code="CODETEXT"}
};
steamCode.ItemsSource = codes.ToString();
}
}
public class Codes
{
public string CodeID { get; set; }
public string Code { get; set; }
}
}
My XAML
<Window x:Class="SteamCodes.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SteamCodes"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ComboBox x:Name="steamCode" ItemsSource="{Binding Source = Codes}" HorizontalAlignment="Left" Height="43" Margin="122,37,0,0" VerticalAlignment="Top" Width="259"/>
</Grid>
</Window>
At the moment my Combo box is Pulling through as each option in the ComboBox is a letter from the line 'System.Collections.Objectmodel.ObservableCollection`1[SteamCodes.Codes]'
Everyone of those letters is a different drop down option in the combo box.
Any Ideas where i have gone wrong.
Your ComboBox ItemSource must be a collection of items, not a string:
steamCode.ItemsSource = codes;
You also have to specify which property of your item must be considered as value to be shown in combobox by setting DisplayMemberPath property:
steamCode.DisplayMemberPath = "Code";
To specify which property of bound objects will be used as actual selected value you have to use SelectedValuePath property:
steamCode.SelectedValuePath = "CodeID";
The MVVM approach is this:
public class ViewModel
{
public ObservableCollection<Codes> Codes { get; }
= new ObservableCollection<Codes>();
}
The MainWindow constructor:
public MainWindow()
{
InitializeComponent();
var viewModel = new ViewModel();
viewModel.Codes.Add(new Codes { CodeID = "1", Code = "CODETEXT" });
DataContext = viewModel;
}
The XAML:
<ComboBox ItemsSource="{Binding Codes}" DisplayMemberPath="Code" .../>
I have a simple wpf app, that I want to try to use ItemsSource binding from xaml. When I click the button. it should be updated also in the UI, but it doesn't.
Why doesn't it work?
Xaml code:
<Window x:Class="SendRawEthernetPacketsGUI.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<ComboBox HorizontalAlignment="Left" Margin="76,65,0,0" VerticalAlignment="Top" Width="120" ItemsSource="{Binding test}"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="90,171,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
C# code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace SendRawEthernetPacketsGUI
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public ObservableCollection<string> test = new ObservableCollection<string>();
public Window1()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
test.Add("fdfddf");
}
}
}
It feels kinda stupid to even ask this, but even looking in google didn't help me. So can you?
Your binding never worked in the first place. You can only bind to properties not fields.
Try this instead:
public ObservableCollection<string> test { get; set; }
public Window1()
{
Test = new ObservableCollection<string>();
}
Or if you want some tricky C# 6 magic:
public ObservableCollection<string> test => new ObservableCollection<string>();
This is a function bodied-member and compiles to a read-only property that is initialized to the new ObservableCollection
Caveats/Design Errors:
Note that in both cases you aren't using INotifyPropertyChanged, so wholesale assignments to the collection won't be picked up by the UI. You should also be using PascalCase for your public properties, and using a proper view model instead of binding to the code-behind.
I've got a weird error when trying to create a simple sample using the latest version of Reactive UI.
The window opens and I get a system error
Couldn't find view for 'Hi Bob!'
note: 'Hi Bob!' is the first item in the list.
What am I missing here?
Thanks.
versions
ReactiveUI 6.5.0
Splat 1.6.2
.net 4.5
Sample code
xaml
<Window x:Class="ListBind.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel Orientation="Horizontal">
<ListBox Name="ListBox1"></ListBox>
</StackPanel>
</Grid>
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ReactiveUI;
namespace ListBind
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, IViewFor<ViewModel>
{
public MainWindow()
{
ViewModel = new ViewModel();
DataContext = ViewModel;
InitializeComponent();
this.OneWayBind(ViewModel, m => m.Items, v => v.ListBox1.ItemsSource);
}
public ViewModel ViewModel
{
get { return (ViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(ViewModel), typeof(MainWindow), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (ViewModel)value; }
}
}
public class ViewModel : ReactiveObject
{
public ReactiveList<string> Items = new ReactiveList<string>(new[] { "Hi Bob!", "Two", "Three" });
}
}
The thing with ReactiveUI when you bind to things like a ListBox using the OneWayBind method, is that it will try to automatically apply a custom template for the data based upon the views it finds with Splat.Locator.Resolve. In your case, it is trying to find and build a view based on the "Hi Bob!" ViewModel, which obviously doesn't exist.
What you should do is force it to use a custom data template so that it doesn't try to apply a non-existing template. With a template below, it shouldn't try and resolve a view for you, but rather stick the "Hi Bob!" value into the TextBlock.
<ListBox x:Name="ListBox1">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
There is a slim chance that ReactiveUI will still ignore that (I cannot verify right now), so if that is the case, replace the OneWayBind binding with the traditional ItemSource={Binding Data}.
All,
I have what i think is the simplest example possible of data binding in silverlight... but clearly even that is too complicated for me :)
The XAML:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
<ListBox x:Name="rblSessions">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SessionTitle}" Foreground="Black" FontSize="30" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
List<Sessions> theSessions = makeSessions();
rblSessions.ItemsSource = theSessions;
rblSessions.DataContext = theSessions;
}
public List<Sessions> makeSessions()
{
List<Sessions> theReturn = new List<Sessions>();
for (int i = 0; i < 20; i++)
{
Sessions s = new Sessions() { SessionID = i, SessionTitle = string.Format("title{0}", i) };
theReturn.Add(s);
}
return theReturn;
}
}
public class Sessions
{
public int SessionID;
public string SessionTitle;
}
}
When I run the app, I get a listbox with 20 elements in it, but each element is empty, and only about 5 pixels tall (though I set FontSize to "30")
What am I doing wrong? Help please and thanks
/jonathan
You must make your Session class members into properties in order to use them in a binding. This should fix it:
public class Sessions
{
public int SessionID { get; set; }
public string SessionTitle { get; set; }
}