I'm working on a WPF project and its MVVM. I got a problem about refreshing combobox binding value. So, here its the case, i have a combobox and button on my grid. I need to change datasource then refresh to see new values according to selected one step before.
Please tell me what is the best method to like next button? After that maybe need to like previous button.
public MyScriptForm(IMyScriptModel viewModel) {
this.Model=viewModel;
InitializeComponent();
Height=Double.NaN;
Width=Double.NaN;
}
public IMyScriptModel Model {
get {
return this.DataContext as IMyScriptModel;
}
set {
this.DataContext=value;
}
}
private void btnNext_Click(object sender,
RoutedEventArgs e) {
/// to what ?
Model.cbxAnswer.Clear();
Model.cbxAnswer.add("Step2Data");
}
Create{//its a huge project, this working on when this form created
myScript.Model.cbxAnswer.Add("1");
myScript.Model.cbxAnswer.Add("2");
myScript.Model.cbxAnswer.Add("3");
}
destroy{}
////////////////////////
//Onmy model
public List<string> cbxAnswer {
get {
return m_cbxAnswer;
}
set {
m_cbxAnswer=value;
OnPropertyChanged("cbxAnswer");
}
}
public List<string> m_cbxAnswer=new List<string>();
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
if (PropertyChanged !=null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
XAML:
<Grid>
<ComboBox Name="cbxAnswer" HorizontalAlignment="Left" Margin="10,130,0,0" VerticalAlignment="Top" Width="130" Height="25" ItemsSource="{Binding Path=cbxAnswer}" />
<Button Name="btnNext" HorizontalAlignment="Left" Margin="215,130,0,0" VerticalAlignment="Top" Width="75" Content="İlerle" Click="btnNext_Click" />
</Grid>
You are just adding and removing from the collection (as opposed to creating a new one). This means that you need a collection that implements INotifyCollectionChanged.
A very convenient class that already does this is ObservableCollection<T>, which I would use instead of List<T> here, and anywhere else you need collection changes to propagate to the UI.
If you are truly doing a full refresh, you may consider just recreating the collection instead of doing add/removes. This could give you a net performance gain, as doing each operation individually must be handled by the UI, as opposed to all at once.
Related
I'm using a GridView in a UserControl to display a five by four square of graphical buttons that allow selection of a Lesson.
This is in a Windows 8.1 Store App that I'm upgrading to Windows 10 UWP.
I previously used Tap and Right-Tap actions to select a Lesson or activate the CommandBar to perform related actions for a Lesson through the SelectionChanged event. However, there have been changes to how Interactions now work under Windows 10, I have been unable to get the Gridview to work at all with binding the SelectedItem to the selected LessonButton in the view model, nor the SelectionChanged and ItemClick events for such purposes. The Gridview selections behaviour doesn't work, as once an item is selected it is never deselected. So finally, I've taken a different tack and am trying Tap and Right-Tap events for the Gridview Items. However the issue is, that no matter which way I approach it, I can't get Binding to work correctly.
So I have an object called LessonButton:
public class LessonButton : INotifyPropertyChanged
{
//public LessonButton() { }
public LessonButton(SolidColorBrush inBackground, bool inComplete, double inHeight, int inNumber, bool inSelected, bool inStarted,
Status inState, double inWidth)
{
...
Started = inStarted;
...
}
...
private bool _started;
public bool Started
{
get { return _started; }
set { if (_started != value) { _started = value; OnPropertyChanged(); } }
}
...
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
It is added to an observable collection in the View Model:
public class LessonsViewModel : INotifyPropertyChanged
{
public ObservableCollection<LessonButton> Lessons { get; } = new ObservableCollection<LessonButton>();
private LessonButton _selectedLessonButton;
public LessonButton SelectedLessonButton
{
get { return _selectedLessonButton; }
set { if (_selectedLessonButton != value) { _selectedLessonButton = value; OnPropertyChanged(); } }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
In a user control I set the DataContext with:
<UserControl.DataContext>
<classes:LessonsViewModel/>
</UserControl.DataContext>
..and I then have a GridView defined as:
<GridView x:Name="LessonGridView" ItemContainerStyle="{StaticResource GridViewItemStyle}" ItemsSource="{Binding Lessons}"
SelectionMode="Single" IsItemClickEnabled="False" SelectedItem="{Binding Path=SelectedLessonButton, Mode=TwoWay}">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid HorizontalChildrenAlignment="Left" MaximumRowsOrColumns="5" Orientation="Horizontal" VerticalChildrenAlignment="Top"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
With the GridView item format defined in a ControlTemplate as part of the GridViewItemStyle.
I've tried to access the LessonButton variables in various ways using Binding and xBind, but could only get the program to run with the ControlTemplate using this XAML:
<Image Grid.Row="1" Grid.Column="1" Width="{StaticResource BadgeSize}"
Height="{StaticResource BadgeSize}" HorizontalAlignment="Right" VerticalAlignment="Top"
Opacity="{Binding Started, Converter={StaticResource OpacityConverterTrueValueIsVisible}}"
Source="/Assets/SelectionButtonGroup/square310x310logobw.png" Stretch="Uniform"/>
The Converter simply returns a 1 or 0 depending upon the value of the bool Started.
Although this code work, it is not correct somehow and Visual Studio reports an unknown error and states it cannot find the Started property. In fact it can't find any of the properties of LessonButton and I've been unable to find the correct syntax for exposing them, even with x:Bind code such as:
{x:Bind LessonViewModel.Lessons.LessonButton.Selected}
..or versions thereof, using casting etc.
I'm using Visual Studio 2017 Enterprise, which reports the aforementioned errors and displays wavy lines over the entire ControlTemplate with an error where it cannot find another Converter artefact that isn't even related to this code.. which in itself, I find extremely irritating. Is it me or does the XAML Intellisence in VS seem very flaky, in that it gives up and reports false errors if it can't identify the root cause of a real one?
Ideally I'd like the Gridview SelectedItem to bind with the ViewModel. But even trying actions via Tap events I can't get the binding to correctly expose LessonButton properties in the ControlTemplate XAML.
Any help would be greatly appreciated.
You shouldn't be using the ItemContainerStyle to Bind your LessonButton variables to. The ItemContainerStyle is used to style the Item with selection marks, its hover and pressed states etc.
You should instead use a DataTemplate stored inside your UserControl's resources like so:
<Grid>
<Grid.Resources>
<DataTemplate x:Name="GridViewTemplate">
<TextBlock Text="{Binding LessonName}">
</DataTemplate>
</StackPanel.Resources>
<GridView x:Name="GridView"
ItemsSource="{Binding Lessons}"
ItemTemplate="{StaticResource GridViewTemplate}">
</GridView>
</Grid>
Then give your DataTemplate a name (above "GridViewTemplate") and set it as the ItemTemplate of your GridView.
I am quiet new to programming and am currently learning C# and the MVVM pattern.
I need to code a database tool for ChiliPlants for university.
There you should be able to add a new object to an ObservableCollection.
To add a new Item to this ObservableCollection a new Window opens. It looks like this:
Window Add
I now want the two RadioBoxes to be bound to a property called "HybridSeed". Which is defined in the ViewModel:
//Public Property HybridSeed
public bool HybridSeed
{
get { return ChiliModel.HybridSeed; }
set
{
if (ChiliModel.HybridSeed == value)
return;
ChiliModel.HybridSeed = value;
OnPropertyChanged("HybridSeed");
}
}
The RadioBox part of my View looks like this:
<RadioButton Grid.Row="5" Content="Ja" Grid.Column="1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<RadioButton Grid.Row="5" Content="Nein" Grid.Column="1" HorizontalAlignment="Left" Margin="89,10,0,0" VerticalAlignment="Top"/>
But how to bind the outcome of a user clicking on these RadioButtons to this HybridSeed Property? Important is that the outcome is a bool.
I looked up almost every entry similar to this topic, but I did not find a simple solution. Or a solution which I was able to understand with my bad coding skills :( ...
I would be very happy if you guys could help me. Please keep it simple for this newbie :)
If there is a simpler solution using a CheckBox or a ComboBox it would also be perfect. The most important thing is to have a nice user interface. Right now it only works with a TextBox where the user always has to write "True" or "False".
Solution:
I added the IsClicked Property in the "Yes" RadioButton to be bound to my boulean property with: IsClicked="{Binding HybridSeed}". Thanks to naslund for his fast answer :)
Just bind HybridSeed to the Yes-radiobutton. It will then either be true if the user has selected that or false if No-radiobutton has been selected (or if nothing has been selected). Binding to both buttons in this case is a bit redundant since the mechanism of radiobuttons takes care of it.
WPF:
<RadioButton Content="Yes" IsChecked="{Binding HybridSeed}" />
<RadioButton Content="No" />
<Label Content="{Binding HybridSeed}" ContentStringFormat="Value is: {0}" />
Logic:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
private bool hybridSeed;
public bool HybridSeed
{
get { return hybridSeed; }
set
{
hybridSeed = value;
OnPropertyChanged(nameof(HybridSeed));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I have a ComboBox bound to an ObservableCollection of objects (with several properties). The Combo Box accurately displays the desired property of all objects and I can select any item from the Combo as expected.
<ComboBox Height="23" Name="comboBox1" Width="120" Margin="5" ItemsSource="{Binding Issues}" DisplayMemberPath="Issue" SelectedValuePath="Issue" SelectedValue="{Binding Path=Issues}" IsEditable="False" SelectionChanged="comboBox1_SelectionChanged" LostFocus="comboBox1_LostFocus" KeyUp="comboBox1_KeyUp" Loaded="comboBox1_Loaded" DropDownClosed="comboBox1_DropDownClosed" IsSynchronizedWithCurrentItem="True" />
I have a series of text boxes which are supposed to display other properties of the selected object. This works fine too.
<TextBox Height="23" Name="textBox5" Width="59" IsReadOnly="True" Text="{Binding Issues/LastSale, StringFormat={}{0:N4}}" />
<TextBox Height="23" Name="textBox9" Width="90" IsReadOnly="True" Text="{Binding Path=Issues/LastUpdate, Converter={StaticResource TimeConverter}}" />
BUT... The properties of ObservableCollection are updated in the Code-Behind on a regular basis and I make a change to the OC by either adding or removing a dummy object in it every time the properties are updated. (I found this simpler than other solutions).
BUT...the data in the TextBoxes DO NOT change! :-( If I select a different object from the ComboBox I get updated info, but it does not change when the OC is changed.
The OC is composed of a bunch of these Objects:
public class IssuesItems
{
public String Issue { get; set; }
public Double LastSale { get; set; }
public DateTime LastUpdate { get; set; }
...
}
The OC is defined as:
public ObservableCollection<IssuesItems> Issues { get; set; }
and instantiated:
this.Issues = new ObservableCollection<IssuesItems>();
What am I doing wrong? Everything I read says that when the LastSale and LastUpdate properties are changed in the OC (and I do something to force an update of the OC) the data in the text boxes ought to change.
ObservableCollection implements INotifyCollectionChanged which allows GUI to refresh when any item is added or deleted from collection (you need not to worry about doing it manually).
But like i mentioned this is restricted to only addition/deletion of items from collection but if you want GUI to refresh when any underlying property gets changed, your underlying source class must implement INotifyPropertyChanged to give notification to GUI that property has changed so refresh yourself.
IssuesItems should implement INPC interface in your case.
Refer to this - How to implement INotifyPropertyChanged on class.
public class IssuesItems : INotifyPropertyChanged
{
private string issue;
public string Issue
{
get { return issue; }
set
{
if(issue != value)
{
issue= value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Issue");
}
}
}
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Implement other properties just like Issue as mentioned above.
I'm trying to solve a simple problem using LINQ, which I'm just learning.
I have a collection of strings, in this case representing serial ports, that will be displayed in a control, but must be ordered. The original collection is unsorted, and I don't necessarily want to modify it, or make a copy of it. So, I created a property of type IEnumerable and bound it to a ComboBox.
This works great, the ComboBox has the correct contents in the correct order. However, if the original collection changes, either a) The ComboBox doesn't get notified properly when the original collection changes, or b) the LINQ query isn't being refreshed.
After trying some different things, I can't see how the following code doesn't work. I must be missing something.
There might be extra code here that is redundant... Anyway, the source followed by the XAML for the controls:
public partial class MainWindow : Window
{
ObservableCollection<string> original = new ObservableCollection<string>();
public ObservableCollection<string> OriginalList {
get { return (original); }
}
private IEnumerable<string> _portList;
public IEnumerable<string> PortList {
get { return (_portList); }
}
public MainWindow() {
InitializeComponent();
original.Add("COM5");
original.Add("COM1");
original.Add("COM3");
original.Add("COM4");
original.Add("COM2");
original.CollectionChanged += new NotifyCollectionChangedEventHandler(OriginalChanged);
_portList = (
from port in original
orderby port ascending
select port
);
DataContext = this;
}
private void AddPortButton_Click(object sender, RoutedEventArgs e) {
original.Add("COM2.5");
}
void OriginalChanged(Object sender, NotifyCollectionChangedEventArgs args) {
NotifyPropertyChanged("PortList");
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
The XAML:
<ComboBox Name="SerialPortListBox" ItemsSource="{Binding PortList}" Width="100" />
<ComboBox Grid.Row="1" Name="OriginalListBox" ItemsSource="{Binding OriginalList}" Width="100" Margin="0,5,0,0"/>
<Button Grid.Column="1" Name="AddPortButton" Content="Add Port 2.5" Width="100" Margin="10,0,0,0" Click="AddPortButton_Click" />
I think you will only get the change notifications you want by binding your ComboBox to an ObservableCollection. This class implements the interface INotifyCollectionChanged, which is what the innards of WPF rely on to notify the UI that updates are required.
I see you are trying to get around this by implementing INotifyPropertyChanged for PortList, however this won't work the way you want. That interface does not trigger the appropriate event to trigger the combobox to refresh. INotifyCollectionChanged tells the listener that a collection has changes (i.e; 'Add', 'Remove', 'Move', 'Replace', 'Reset'), whereas INotifyPropertyChanged only indicates that some value has changed in the bound object. The ComboBox will not respond to an INotifyPropertyChanged event, in fact it's probably not even subscribing to events of that type.
So, either bind directly to the underlying datasource, or implement a 2nd ObservableCollection on top of that, rather than only an IEnumerable, which does not notify on change.
I'm trying to update a property using a PropertyChangedEventHandler, but I think my conceptual understanding of how this works might be a bit flawed. As I'm new to WPF and silver-light.
So, let me explain, I have a property that is set to 0, but after some time a thread changes the value from 0 to 9 internally, but despite the change in value, this property never gets updated in the actual view and I don't know why! Even after I implement a PropertyChangedEventHandler there is no change, but if I log the property it shows that the value is in fact 9
So here is the snippet of code that implements PropertyChangedEventHandler:
public class CustomColumn : IColumnViewable, INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void OnPropertyChanged(string propertyName)
{
Foo.log.Error(": start on property change");
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
Foo.log.Error(": end on property change");
}
public static string _total;
public string total { get { return _total; } set { _total = value; OnPropertyChanged("total"); Foo.log.Error(": property change"); } }
...
}
Here is part of my xaml:
<DataTemplate x:Key="ColumnView">
<UserControl HorizontalAlignment="Stretch">
<StackPanel HorizontalAlignment="Stretch">
...
<RichTextBox Margin="5,2,5,2">
<Paragraph>
<Run Text="{Binding Path=total, Mode=OneWay}" FontWeight="Bold" FontSize="30" />
<Run Text=" total clicks" FontWeight="Bold" />
</Paragraph>
</RichTextBox>
...
<ContentControl VerticalAlignment="Stretch" Content="{Binding Path=timeline}" ContentTemplate="{Binding Path=timelineView.ContentTemplate}" />
</StackPanel>
</UserControl>
</DataTemplate>
And I do this on initialize:
CustomColumn content = new CustomColumn();
content.total = "0";
And then I pass the object to a thread which at some point does this:
content.total = "9";
Foo.log.Error("value is "+content.total);
And the property never updates and I don't know why - any help is greatly appreciated
If I understand the details of your question, you are updating a UI bound value on a background thread. You need to make that happen on the UI thread or the change will not be visible. In one of our WPF apps random updates were disappearing until we realised this.
We do a lot of multi-threading in our Silverlight (and WPF) apps so to avoid this problem, we implemented our notify helper in a base class like the one below (other stuff trimmed out). It dispatches all notify messages on the main UI thread. Give it a try:
public class ViewModelBase : INotifyPropertyChanged
{
protected delegate void OnUiThreadDelegate();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void SendPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// Ensure property change is on the UI thread
this.OnUiThread(() => this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
}
}
protected void OnUiThread(OnUiThreadDelegate onUiThreadDelegate)
{
// Are we on the Dispatcher thread ?
if (Deployment.Current.Dispatcher.CheckAccess())
{
onUiThreadDelegate();
}
else
{
// We are not on the UI Dispatcher thread so invoke the call on it.
Deployment.Current.Dispatcher.BeginInvoke(onUiThreadDelegate);
}
}
}
Your code does not show where you make the object the DataContext of your controls, which is necessary for your bindings which do not specify another source and hence bind to the DataContext.
CustomColumn content = new CustomColumn();
content.total = "0";
Do you have any line after this where this object is passed to your view?