Im missing something but i have no idea what :(
I've bind IEnumerable collection to combobox. I would like to use its selected value as parameter for shutdown command, however when i press start button it doesn't load selected values.
I've followed few tutorials to understand MVVM but there is still something missing but i cant figured out what.
Here is MainWindow.xaml :
<grid>
<StackPanel>
<!--Title label-->
<TextBlock Text="Wyłącz komputer za:" Margin="5"/>
<!-- Blocks used to set hours and minutes-->
<StackPanel Orientation="Horizontal">
<TextBlock Text="Godziny:" Margin="5"/>
<ComboBox x:Name="HoursCB" Margin="5" Width="40" ItemsSource="{Binding myHours}" SelectedValue="{Binding selectedHours, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Minuty:" Margin="5" />
<ComboBox x:Name="MinutesCB" Margin="5" Width="40" ItemsSource="{Binding myMinutes}" SelectedValue="{Binding selectedMinutes, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel >
<!-- Timer -->
<StackPanel Orientation="Horizontal">
<Label x:Name="HHLabel" Content="{Binding selectedHours}" FontSize="30" HorizontalAlignment="Center" Width="45"/>
<Label x:Name="Colon1" Content=":" FontSize="30" HorizontalAlignment="Center" Width="25"/>
<Label x:Name="MMLabel" Content="{Binding selectedMinutes}" FontSize="30" HorizontalAlignment="Center" Width="45"/>
<Label x:Name="Colon2" Content=":" FontSize="30" HorizontalAlignment="Center" Width="25"/>
<Label x:Name="SSLabel" Content="00" FontSize="30" HorizontalAlignment="Right" Width="45"/>
</StackPanel>
<!-- Start Button -->
<Button Content="uruchom odliczanie" Margin="5" Command="{Binding StartCommand}" />
<!-- Stop Button-->
<Button Content="Zatrzymaj odliczanie" Margin="5" Command="{Binding StopCommand}"/>
</StackPanel>
</grid>
and here is view model :
class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
StartCommand = new AddNameCommand(this);
}
class AddNameCommand : ICommand
{
MainWindowViewModel parent;
public AddNameCommand(MainWindowViewModel parent)
{
this.parent = parent;
parent.PropertyChanged += delegate { CanExecuteChanged?.Invoke(this, EventArgs.Empty); };
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{ return true; }
public void Execute(object parameter)
{
int num = parent.SelectedHours * 3600 + parent.SelectedMinutes * 60;
MessageBox.Show($"Shutting down the computer in {num} !");
//Process.Start("shutdown", string.Format("/s /t {0}", num));
}
}
public ICommand StartCommand { get; private set; }
/// <summary>
/// Combobox Items.
/// </summary>
//public IEnumerable<int> myHours = Enumerable.Range(0, 23);
//public IEnumerable<int> myMinutes = Enumerable.Range(1, 59);
public ObservableCollection<int> myHours { get; set; } = new ObservableCollection<int>(Enumerable.Range(0, 23));
public ObservableCollection<int> myMinutes { get; set; } = new ObservableCollection<int>(Enumerable.Range(1, 59));
/// <summary>
/// Selected time properties.
/// </summary>
public int SelectedMinutes
{
get { return mSelectedMinutes; }
set
{
if (value == mSelectedMinutes)
return;
mSelectedMinutes = value;
OnPropertyChanged();
}
}
int mSelectedMinutes;
public int SelectedHours
{
get { return mSelectedHours; }
set
{
if (value == mSelectedHours)
return;
mSelectedHours = value;
OnPropertyChanged();
}
}
int mSelectedHours;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
}
You should change your xaml to the following (note the case sensitive SelectedHours/Minutes):
<!-- Blocks used to set hours and minutes-->
<StackPanel Orientation="Horizontal">
<TextBlock Text="Godziny:" Margin="5"/>
<ComboBox x:Name="HoursCB" Margin="5" Width="40" ItemsSource="{Binding myHours}" SelectedValue="{Binding SelectedHours, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Minuty:" Margin="5" />
<ComboBox x:Name="MinutesCB" Margin="5" Width="40" ItemsSource="{Binding myMinutes}" SelectedValue="{Binding SelectedMinutes, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel >
Binding errors can be easily detected by opening the Output Window in Visual Studio and looking for the following:
System.Windows.Data Error: 40 : BindingExpression path error: 'selectedHours' property not found on 'object' ''MainWindowViewModel' (HashCode=46431654)'. BindingExpression:Path=selectedHours; DataItem='MainWindowViewModel' (HashCode=46431654); target element is 'ComboBox' (Name='HoursCB'); target property is 'SelectedValue' (type 'Object')
You have not binded the Selected Values of combo boxes to the right property in the View Model. Just change "selectedHours" to "SelectedHours" and "selectedMinutes" to "SelectedMinutes" in your XAML to bind it properly.
You code is behaving like i expedted it. Your problem is the Enumerable.Range function. It starts with an inclusive 0 and then counts up 23 times Inclunding 0
Related
So here I have a MVVM form. the Form contains a Datagrid which is connected to the Databank. I also have a ComboBox which I want to use as a filter option. The Filter option shoud filter by the "AnlV nr" so when the user selects "01" from the ComboBox the datagrid should refresh and show only that "AnlV nr" that have "01" Below I will share you the code and you can see that i've gotten as far as showing the "AnlV" values in the ComboBox but I now do not know how to do the rest and make the filter work. Below is my Viewmodel and the Xaml code.
If anyone can help me with this I would really apreciate it.
Xaml Code:
<Window x:Class="QBondsFrontend.Views.Input.AnlVTexteView"
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:QBondsFrontend.Views.Input" xmlns:input="clr-namespace:QBondsFrontend.ViewModels.Input" d:DataContext="{d:DesignInstance Type=input:AnlVTexteViewModel}"
mc:Ignorable="d"
Title="AnlVTexteView"
Width="800"
MinHeight="400"
Height="490"
MinWidth="1010"
MaxWidth="1010"
UseLayoutRounding="True">
<Grid Background="#A8A8A8" >
<StackPanel VerticalAlignment="Top" Background="#A8A8A8" Orientation="Horizontal" Height="57">
<Label
Content="AnlV Nr.:" Height="35" FontSize="12"/>
<ComboBox Background="LightGray" Height="20" Width="70" ItemsSource="{Binding lstAnlVTexte}" SelectedItem="{Binding Search}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding AnlVPara}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Height="18" Width="68" Margin="5, 0"
Content="Filter löschen" FontSize="11" Style="{StaticResource STL_ButtonStandard}"
x:Name="filterlöschen"
Command="{Binding icdFilterDelete}"/>
</StackPanel>
<StackPanel Background="LightGray" VerticalAlignment="Top" Height="177" Margin="0,57,0,0">
<DataGrid x:Name="datagridXAML"
Height="177"
ItemsSource="{Binding lstAnlVTexte, Mode=TwoWay}"
Style="{StaticResource STL_DataGridReporting}"
CellStyle="{StaticResource STL_DataGridCellReporting}"
ColumnHeaderStyle="{StaticResource STL_DataGridColumnHeaderReporting}"
AlternatingRowBackground="#A8A8A8"
CanUserResizeColumns="False"
>
<DataGrid.Columns>
<DataGridTextColumn Header="AnlV-Nr"
Binding="{Binding AnlVPara}"
Width="60"/>
<DataGridTextColumn Header="gültig ab"
Binding="{Binding TextGueltigAb}"
Width="68"/>
<DataGridTextColumn Header="Text"
Binding="{Binding ParaText}"
Width="750"/>
<DataGridTextColumn Header="Info"
Binding="{Binding Info}"
Width="*"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<StackPanel Background="#A8A8A8" HorizontalAlignment="Center" Margin="10,268,0,141" Width="1010" >
<Label Content="Bearbeitungsbereich" FontWeight="Bold" FontSize="12" Height="33" />
</StackPanel>
<StackPanel>
<StackPanel Orientation="Horizontal" Background="#A8A8A8" HorizontalAlignment="Center"
Width="1010" Margin="0,294,0,0" Height="31">
<Label Height="25" Width="60" Margin="20, 0, 0, 0" Content="AnlV-Nr.:" />
<ComboBox IsEditable="True" Background="gray" Height="22" Width="69" ItemsSource="{Binding AnlVPara}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding lstAnlVTexte}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Height="15" Margin="10, 0, 0, 0" />
<Label Height="26" Width="122" Content="Editierwarnungen" />
<StackPanel Height="48" Width="100"/>
</StackPanel>
<StackPanel Height="22" Orientation="Horizontal">
<Label Margin="20, 0, 0, 0" Content="gültig ab:" Height="27" />
<TextBox Background="LightGray" Height="20" Width="100" />
</StackPanel>
<StackPanel Height="50" Orientation="Horizontal">
<Label Content="Text:" Height="27" Width="38" Margin="42,0,0,10" />
<TextBox Background="LightGray" Width="500" Height="43" />
</StackPanel>
<StackPanel Orientation="Horizontal" >
<Label Content="Info:" Height="27" Width="38" Margin="42,0,0,0" />
<TextBox Background="LightGray" Width="500" Height="20" />
<Button x:Name="BTN_speichern" Width="80" Height="18" Margin="20,0,0,0" Content="Speichern"
Style="{StaticResource STL_ButtonStandard}" Command="{Binding icdSpeichern}"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
ViewModel:
using Newtonsoft.Json;
using QBondsData.DBModels;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Linq;
namespace QBondsFrontend.ViewModels.Input
{
public class AnlVTexteViewModel : BaseViewModel
{
#region data
private string _AnlVPara;
private DateTime _TextGueltigAb;
private string _ParaText;
private string _Info;
private List<AnlVhistText> _lstAnlVTexte;
private string _search;
#endregion
#region constructor
public AnlVTexteViewModel()
{
icdFilterDelete = new RelayCommand<object>(parameter => filterdelete(), parameter => true);
icdSpeichern = new RelayCommand<object>(parameter => speichern(), parameter => true);
GetData();
}
#endregion
#region members
public ICommand icdFilterDelete { get; set; }
public ICommand icdSpeichern { get; set; }
private string Search
{
get { return _search; }
set
{
_search = value;
OnPropertyChanged("Search");
}
}
public string AnlVPara
{
get
{
return _AnlVPara;
}
set
{
_AnlVPara = value;
OnPropertyChanged("AnlVPara");
}
}
public DateTime TextGueltigAb
{
get
{
return _TextGueltigAb;
}
set
{
_TextGueltigAb = value;
OnPropertyChanged("TextGueltigAb");
}
}
public string ParaText
{
get
{
return _ParaText;
}
set
{
_ParaText = value;
OnPropertyChanged("ParaText");
}
}
public string Info
{
get
{
return _Info;
}
set
{
_Info = value;
OnPropertyChanged("Info");
}
}
public List<AnlVhistText> lstAnlVTexte
{
get { return _lstAnlVTexte; }
set
{
_lstAnlVTexte = value;
OnPropertyChanged("lstAnlVTexte");
}
}
#endregion
#region methods
private void filterdelete()
{
}
private void speichern()
{
}
private async Task GetData()
{
HttpResponseMessage Response = await Globals.SendRequest("AnlVTexte/GetAnlVTexte"
, "GET");
if (Response.IsSuccessStatusCode)
{
lstAnlVTexte = JsonConvert.DeserializeObject<List<AnlVhistText>>
(await Response.Content.ReadAsStringAsync());
}
else
{
lstAnlVTexte = new List<AnlVhistText>();
Application.Current.Dispatcher.Invoke((Action)delegate
{
Globals.CloseReportByHash(this.GetHashCode()
, "Fehler! (HTTP Status " + Response.StatusCode + ")." +
"Kontaktieren Sie den Support.");
});
}
}
#endregion
}
}
When you change the type of lstAnlVTexte to ICollectionView you get two events CurrentChanged and CurrentChanging which you can handle in your viewmodel. From the ICollectionView you can get the CurrentItem.
Like this:
private List<AnlVhistText> _anlVTexte = new List<AnlVhistText>();
public AnlVTexteViewModel()
{
[...]
lstAnlVTexte = new CollectionView(_anlVTexte);
lstAnlVTexte.CurrentChanged += SelectionChanged; // raised after the current item has been changed.
lstAnlVTexte.CurrentChanging += SelectionChanging; // raise before changing the current item. Event handler can cancel this event.
}
private void SelectionChanged(object sender, EventArgs e)
{
var selectedItem = lstAnlVTexte.CurrentItem;
}
private void SelectionChanging(object sender, CurrentChangingEventArgs e)
{
}
private ICollectionView _lstAnlVTexte;
public ICollectionView lstAnlVTexte
{
get { return _lstAnlVTexte; }
set
{
_lstAnlVTexte = value;
OnPropertyChanged("lstAnlVTexte");
}
}
Here's a sample using the community toolkit mvvm and linq.
If you're not familiar, the toolkit does code generation.
This is a simple scenario to illustrate the approach.
Mainwindowviewmodel.
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private int selectedFilterInt = -1;
partial void OnSelectedFilterIntChanged(int newValue)
{
FilteredList = new ObservableCollection<MyItem>(FullList.Where(x=>x.Category == selectedFilterInt).ToList());
}
public List<int> FilterOptions { get; set; } = new List<int> {1,2,3};
private List<MyItem> FullList= new List<MyItem>
{
new MyItem{ Category = 1},
new MyItem{ Category = 1},
new MyItem { Category = 1 },
new MyItem { Category = 2 },
new MyItem { Category = 2 },
new MyItem { Category = 3 }
};
[ObservableProperty]
private ObservableCollection<MyItem> filteredList = new ObservableCollection<MyItem>();
public MainWindowViewModel()
{
FilteredList = new ObservableCollection<MyItem>(FullList);
}
}
There's a full list of all the items.
But a filtered list is going to be bound to the itemssource of my listbox ( equivalent to your datagrid ).
Due to the code generated, when selectedFilterInt changes, OnSelectedFilterIntChanged will be called. It's got a handler listening for property changed of SelectedFilterInt if you dug into the generated code.
That method uses a linq where to filter the full list into filtered list.
Setting that filtered list property raises property changed and the view re reads the new collection.
MainWindow. ( I did mention this is simplified )
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<ComboBox SelectedItem="{Binding SelectedFilterInt}"
ItemsSource="{Binding FilterOptions}"/>
<ListBox ItemsSource="{Binding FilteredList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Category}"/>
<TextBlock Text="{Binding Comment}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Window>
and MyItem
public partial class MyItem : ObservableObject
{
[ObservableProperty]
private int category = 0;
[ObservableProperty]
private string comment = "Some test string";
}
Which is a bit underwhelming visually but works:
In your code you need to get all the data into a collection.
Call that FulList.
You then need another collection which will be the filtered data.
Call this FilteredList.
Bind itemssource to FilteredList
Initially, you presumably want FilteredList to be = FullList
Then when the user selects something in the combobox you need to react to that.
You could bind selecteditem to a property and act in the setter or handle propertychanged like my code does.
However you do it, you get a new integer.
You then use that to filter FullList into a new collection which will replace the bound FilteredList.
You also need to somehow have one entry per AnlV nr whatever that is in your combobox.
AnlV nr isn't going to work as a property name since it's got a space but it is the equivalent to Category in my sample.
You will use that selected value in the linq.
Substitute the name of that property for Category. Substitute the type of whatever your collection is. Maybe that's AnlVhistText. I'm not sure.
In the application there is a Listbox bound to an ObservableCollection, then the selected item is bound itself to some labels: when a property of the item is changed in the label the actual object (in this case Multimedia) is updated (as I debugged) but then the listbox doesn't update the displayed value.
The Multimedia class implements INotifyPropertyChanged but I'm not sure if I am using it correctly.
Everything else is working without any problem (the add button adds a new element to the list and it is displayed as it should).
I looked around on different forums and also on stackoverflow and tried different variants but still the property, when updated, it is not updated in the ListBox.
This is the XMAL:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="135" />
<RowDefinition Height="*" />
<RowDefinition Height="45" />
</Grid.RowDefinitions>
<ListBox Name="mediaListBox" ItemsSource="{Binding Path=MyData}" Grid.Row="0"/>
<Grid Grid.Row="1" DataContext="{Binding ElementName=mediaListBox, Path=SelectedItem}">
...
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Path=Title}" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=Artist}" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Path=Genre}" />
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding Path=Type}" />
</Grid>
<Button Name="cmdAddMedia" Grid.Row="1" Click="cmdAddMedia_Click" Height="45" Margin="0,0,0,2" Grid.RowSpan="2" VerticalAlignment="Bottom">Add Item</Button>
</Grid>
Then here there is the C# code of the main window:
public partial class MainWindow : Window
{
public MultiMediaList MyData { get; set; }
public void AddStuff()
{
MyData.Add(new Multimedia() { Title = "My Way", Artist = "Calvin Harris", Genre = "Pop", Type = Multimedia.MediaType.CD });
MyData.Add(new Multimedia() { Title = "Inglorious Bastards", Artist = "Quentin Tarantino", Genre = "Violence", Type = Multimedia.MediaType.DVD });
}
public MainWindow()
{
MyData = new MultiMediaList();
AddStuff();
InitializeComponent();
DataContext = this;
}
...
}
And finally the Multimedia class and the MultiMediaList class:
public class Multimedia : INotifyPropertyChanged
{
public enum MediaType { CD, DVD };
private string _title;
private string _artist;
private string _genre;
private MediaType _type;
public string Title
{
get { return _title; }
set
{
_title = value;
NotifyPropertyChanged("Title");
}
}
...
public override string ToString()
{
return _title + " - " + _artist + " [" + _genre + "] - " + getTypeString();
}
private string getTypeString()
{
if(Type == MediaType.CD) { return "CD"; }
else { return "DVD"; }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
MultimediaList is just an empty class inheriting from ObservableCollection
public class MultiMediaList: ObservableCollection<Multimedia>
{
}
If you need I can also upload the full code
Hope you can help me and tell me what I am doing wrong.
Apparently you are expecting that the ListBox automagically calls the Multimedia object's ToString() method whenever one if its properties changes. That's not the case.
Instead of relying on ToString, declare a proper ItemTemplate for the ListBox:
<ListBox Name="mediaListBox" ItemsSource="{Binding MyData}" Grid.Row="0">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Title}"/>
<Run Text="-"/>
<Run Text="{Binding Artist}"/>
<Run Text="["/><Run Text="{Binding Genre}"/><Run Text="]"/>
<Run Text="{Binding Type}"/>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The TextBlock might be written shorter:
<TextBlock>
<Run Text="{Binding Title}"/> - <Run Text="{Binding Artist}"/> [<Run Text="{Binding Genre}"/>] <Run Text="{Binding Type}"/>
</TextBlock>
this is xaml part
<ItemsControl x:Name="EventsTop">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,1,0,0">
<Button Content="{Binding Name}" Template="{DynamicResource ButtonFirst}" Height="50" Margin="15,0,0,0" Padding="10,0,15,0" FontSize="19" FontFamily="/Resources/Fonts/Font Awesome/#FontAwesome" BorderThickness="5,0,0,0" BorderBrush="#8CC152" Background="#2980B9" HorizontalContentAlignment="Left" Foreground="Black" Click="TabOpen" Tag="{Binding Id}"></Button>
<StackPanel Background="#2980B9" Margin="15,0,0,5" Visibility="Collapsed" AllowDrop="True" Tag="{Binding Id}" Drop="RowDrop">
<Border BorderThickness="5,0,0,0" BorderBrush="#8CC152">
<StackPanel>
<DockPanel LastChildFill="False">
<Label DockPanel.Dock="Left" Width="140" Content="Date" FontSize="19" BorderThickness="0,0,0,1" FontFamily="/Resources/Fonts/Open Sans/#Open Sans" BorderBrush="Black" HorizontalContentAlignment="Center"></Label>
<Label DockPanel.Dock="Left" Width="190" Content="Event" FontSize="19" BorderThickness="0,0,0,1" FontFamily="/Resources/Fonts/Open Sans/#Open Sans" BorderBrush="Black" HorizontalContentAlignment="Center"></Label>
<Label DockPanel.Dock="Left" Width="100" Content="Select" FontSize="19" BorderThickness="0,0,0,1" FontFamily="/Resources/Fonts/Open Sans/#Open Sans" BorderBrush="Black" HorizontalContentAlignment="Center"></Label>
</DockPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto" MaxHeight="150">
<ItemsControl ItemsSource="{Binding Details}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel LastChildFill="False">
<Label Content="{Binding Date}" DockPanel.Dock="Left" Width="140" FontSize="19" BorderThickness="0" FontFamily="/Resources/Fonts/Open Sans/#Open Sans" BorderBrush="Black" HorizontalContentAlignment="Center"></Label>
<Label Content="{Binding EventName}" DockPanel.Dock="Left" Width="165" FontSize="19" BorderThickness="0" FontFamily="/Resources/Fonts/Open Sans/#Open Sans" BorderBrush="Black" HorizontalContentAlignment="Center"></Label>
<Border Width="97">
<CheckBox VerticalAlignment="Center" HorizontalAlignment="Center" IsChecked="{Binding Checked}"></CheckBox>
</Border>
<Button Width="25" DockPanel.Dock="Left" Content="" BorderThickness="0" Background="Transparent" FontFamily="/Resources/Fonts/Font Awesome/#FontAwesome"></Button>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</StackPanel>
</Border>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
this is xaml.cs
private void WindowLoaded(object sender, RoutedEventArgs e)
{
EventHelper eventHelper = new EventHelper();
TopEvents = eventHelper.GetSports(EventHelper.EventGroup.Top);
foreach (Sport item in TopEvents)
{
item.Name = "\uf196 " + item.Name;
}
EventsTop.ItemsSource = TopEvents;
AllEvents = eventHelper.GetSports(EventHelper.EventGroup.All);
foreach (Sport item in AllEvents)
{
item.Name = "\uf196 " + item.Name;
}
EventsAll.ItemsSource = AllEvents;
Sport.ItemsSource = eventHelper.GetSports(EventHelper.EventGroup.All);
}
private void RowMouseDown(object sender, MouseButtonEventArgs e)
{
DockPanel currentRow = (DockPanel) sender;
int rowOffset = Convert.ToInt32(currentRow.Tag);
DragDrop.DoDragDrop(currentRow,rowOffset,DragDropEffects.Copy);
}
private void RowDrop(object sender, DragEventArgs e)
{
int rowOffset = (int) e.Data.GetData(typeof (int));
AllEvents[0].Name = "1";
}
Also my model in collection
class Sport : INotifyPropertyChanged
{
private int _id;
private string _name = string.Empty;
private ObservableCollection<Details> _details = new ObservableCollection<Details>();
public int Id
{
get { return _id; }
set { _id = value; }
}
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Content");
}
}
public ObservableCollection<Details> Details
{
get { return _details; }
set { _details = value; }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
MessageBox.Show(info);
}
}
}
So when I am changing property its throwing MessageBox but not updating GUI.
I xaml.cs I am calling methods GetEvents thats
return ObservableCollection
I want to change Name in Sport which is in ObservableCollaction<Sport> AllEvents
You can see it in RowDrop method in xaml.cs
In debugging I notice that AllEvents[0].Name was changed but view was not updating
UPDATE
Part of ObservabelCollection declaration
public MainPage()
{
InitializeComponent();
AllEvents = new ObservableCollection<Sport>();
TopEvents = new ObservableCollection<Sport>();
EventsTop.ItemsSource = TopEvents;
EventsAll.ItemsSource = AllEvents;
}
private ObservableCollection<Sport> AllEvents;
private ObservableCollection<Sport> TopEvents;
UPDATE SECOND
I caught that when I am using window activated event it is working
I found solution.
So, ObservableCollection is working very well but,It needs to be refreshed
for appearing in view and for it we need to use
CollectionViewSource.GetDefaultView(ObservableCollection).Refresh()
method for it
I think it will help someone
The problem with property named passed to NotifyPropertyChanged method. The name of parameter should be property name. Please change the Name property as
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
Use CallerMemberNameAttribute to avoid having to get the name correct and allowing refactoring:
private void NotifyPropertyChanged([CallerMemberName] string info = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
MessageBox.Show(info);
}
}
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged(); //now no need to specify
}
}
Every property setter should notify property change, so:
public IEnumerable<Details> Details //note IEnumerable, no calling code needs to know its concrete type
{
get { return _details; }
set
{
_details = value;
NotifyPropertyChanged();
}
}
And with an observable range collection you could do this:
private readonly ObservableRangeCollection<Details> _details = new ObservableRangeCollection<Details>();
public IEnumerable<Details> Details
{
get { return _details; }
set { _details.Replace(value); }
}
From MSDN.
Occurs when an item is added, removed, changed, moved, or the entire
list is refreshed.
The changed does not mean when child properties are changed, but when you change the item at any index.
So when you modify a collection item you will need to notify the binding that the property was changed. From your window's viewmodel after you have modified the item in the collection, you would notify that the collection was changed.
NotifyPropertyChanged("AllEvents");
I need to bind the text box with the data available on it and execute a command associate with that. I want the data entered as well command execution only when "Enter" button on keyboard is pressed. I used to the below code, but it seems, I am getting command execution without "Enter" is pressed also found that for each number or text pressed, I am getting the command. I don't want this to happen.
my InputDataTemplate.xaml code:
<Label Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Content="{Binding Name}" />
<Label Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Center" Content="{Binding Value}" />
<TextBox Grid.Column="1" Width="60" HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding Data}" DataContext="{Binding}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged" >
<ei:CallMethodAction TargetObject="{Binding}" MethodName="IpDataTrig" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
I can understand EventName="TextChanged" plays a role here. But not sure about the other stuffs.
My TesterView.xaml code:
<UserControl.Resources>
<DataTemplate x:Key="InputDataTemplate" >
<local:InputDataTemplate DataContext="{Binding}" />
</DataTemplate>
</UserControl.Resources>
<Grid Grid.Row="2" Background="AliceBlue" >
<Label Content="Input Datas" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
<Border Grid.Row="3" BorderBrush="Black" BorderThickness="0,2,0,0" >
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" >
<ItemsControl x:Name="IpDataNames"
DataContext="{Binding }"
ItemsSource="{Binding IpDataNames}"
ItemTemplate="{DynamicResource InputDataTemplate}" />
</ScrollViewer>
</Border>
my TesterViewModel.cs:
private ObservableCollection<InputDataService> _IpDataNames;
private InputDataService l_IpDataNames;
_IpDataNames = new ObservableCollection<InputDataService>();
public ObservableCollection<InputDataService> IpDataNames
{
get { return _IpDataNames; }
set
{
IpDataNames = value;
}
}
InputDataService.cs:
public class InputDataService : BindableBase
{
public string Name { get; set; }
public string Value { get; set; }
public string Data { get; set; }
public void IpDataTrig()
{
Debug.WriteLine(string.Format("\nInput Data {0} Updated : {1} : {2}", Name, Data, Value));
}
}
Possible duplicate question: https://stackoverflow.com/a/10466285/475727
BTW, nothing is wrong about capturing KeyPress event and calling command from codebehind. It is not violation of MVVM pattern.
Sometimes I use my own behavior implemented as attached property. Big advantage is, that I can use it in styles.
This behaviour update binding source on text property and then calls command. (TextBox.Text binding is updated on losf focus by default)
public static class TextBoxBehaviour
{
public static readonly DependencyProperty CommandOnEnterPressedProperty = DependencyProperty.RegisterAttached("CommandOnEnterPressed",typeof (ICommand),typeof (TextBoxBehaviour),
new FrameworkPropertyMetadata(CommandOnEnterPressedPropertyChanged));
private static void CommandOnEnterPressedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sender = (TextBox) d;
sender.KeyDown -= OnKeyDown;
if (e.NewValue is ICommand)
{
sender.KeyDown += OnKeyDown;
}
}
private static void OnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
var tbx = (TextBox) sender;
var textBindigExpression = tbx.GetBindingExpression(TextBox.TextProperty);
if (textBindigExpression != null)
{
textBindigExpression.UpdateSource();
}
var command = GetCommandOnEnterPressed(tbx);
if (command.CanExecute(null)) command.Execute(null);
}
}
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static void SetCommandOnEnterPressed(TextBox elementName, ICommand value)
{
elementName.SetValue(CommandOnEnterPressedProperty, value);
}
public static ICommand GetCommandOnEnterPressed(TextBox elementName)
{
return (ICommand) elementName.GetValue(CommandOnEnterPressedProperty);
}
}
and usage
<TextBox Text="{Binding SearchTerm, UpdateSourceTrigger=Explicit}"
my:TextBoxBehaviour.CommandOnEnterPressed="{Binding SearchCommand}"/>
To start off, I have a listbox that is trying to accept a UserControl as the DataTemplate:
<ListBox VerticalAlignment="Stretch" Name="GeneralMcmView" Grid.Column="0" HorizontalAlignment="Stretch" >
<ListBox.ItemTemplate>
<DataTemplate DataType="local:GeneralMcmMessage">
<local:GeneralMcmMessage />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
With the contents of the usercontrol looking like:
<ContentControl FontFamily="Segoe UI" VerticalAlignment="Stretch" FontSize="10">
<Grid VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel VerticalAlignment="Stretch" Orientation="Horizontal" Grid.Row="0">
<TextBlock Name="MessageDateTime" Text="{Binding ElementName=_this, Path=TimeStamp, StringFormat=MM/dd/yyyy h:mm:ss.fff tt \'GMT\' (zzz)}" />
<TextBlock Name="MessageTypeLabel" Margin="15,0,5,0" Text="Type:"/>
<TextBlock Name="MessageType" Text="{Binding ElementName=_this, Path=Type}" />
</StackPanel>
<StackPanel VerticalAlignment="Stretch" Orientation="Horizontal" Grid.Row="1">
<TextBlock Name="MessageNameLabel" Margin="0,0,5,0" Text="Message Name:" />
<TextBlock Name="MessageNameValue" Text="{Binding ElementName=_this, Path=MessageName}" TextWrapping="Wrap" />
</StackPanel>
<StackPanel VerticalAlignment="Stretch" Orientation="Vertical" Grid.Row="2">
<TextBlock Name="MessageLabel" Text="Message:"/>
<TextBlock Name="Message" Margin="10,0,0,0" Text="{Binding ElementName=_this, Path=MessageContent}" />
</StackPanel>
</Grid>
</ContentControl>
I then create a couple messages, all with different data (The Listbox's ItemSource is bound to the GeneralMessages ObservableCollection):
GeneralMcmMessage newMsg = new GeneralMcmMessage()
{
MessageId = e.McmMessageViewInfo.Id,
TimeStamp = e.McmMessageViewInfo.MessageDateTime,
Type = e.McmMessageViewInfo.MessageType.ToString(),
MessageName = e.McmMessageViewInfo.MessageName,
MessageContent = e.McmMessageViewInfo.Message.ToString()
};
GeneralMessages.Add( newMsg );
During runtime I interrogate the Items property of the listbox and all the data looks correct, however all I see in the listbox are entries of the GeneralMcmMessage User Control with default data values. Any ideas as to why?
Also, FWIW I am using INotifyPropertyChanged in the usercontrol class:
public partial class GeneralMcmMessage : UserControl, INotifyPropertyChanged
{
private Constants.PiuModule piuModule = Constants.PiuModule.MCM;
private string className = "GeneralMcmMessage";
/// <summary>
/// Event for notifying listeners that a property changed. Part of INotifyPropertyChanged
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public int MessageId { get; set; }
private DateTime timeStamp;
public DateTime TimeStamp
{
get
{
return timeStamp;
}
set
{
timeStamp = value;
OnNotifyPropertyChanged( "TimeStamp" );
}
}
You're saying that the DataTemplate to display a GeneralMcmMessage is to instantiate new GeneralMcmMessage.
<DataTemplate DataType="local:GeneralMcmMessage">
<local:GeneralMcmMessage />
</DataTemplate>
I'd recommend instead of making a collection of your UserControl to make a collection of your model object instead, and bind to the properties within that within your UserControl.
Either way though - the object you created in code will be the DataContext for the one you created in XAML so removing the ElementName=_this should bind appropriately. Try this simplified XAML in your UserControl instead.
<StackPanel VerticalAlignment="Stretch" Orientation="Horizontal" Grid.Row="0">
<TextBlock Name="MessageDateTime" Text="{Binding TimeStamp, StringFormat=MM/dd/yyyy h:mm:ss.fff tt \'GMT\' (zzz)}" />
<TextBlock Name="MessageTypeLabel" Margin="15,0,5,0" Text="Type:"/>
<TextBlock Name="MessageType" Text="{Binding Type}" />
</StackPanel>
<StackPanel VerticalAlignment="Stretch" Orientation="Horizontal" Grid.Row="1">
<TextBlock Name="MessageNameLabel" Margin="0,0,5,0" Text="Message Name:" />
<TextBlock Name="MessageNameValue" Text="{Binding MessageName}" TextWrapping="Wrap" />
</StackPanel>
<StackPanel VerticalAlignment="Stretch" Orientation="Vertical" Grid.Row="2">
<TextBlock Name="MessageLabel" Text="Message:"/>
<TextBlock Name="Message" Margin="10,0,0,0" Text="{Binding MessageContent}" />
</StackPanel>
You don’t post all code of GeneralMcmMessage hence I don’t know if you set DataContext in user control for example in constructor of GeneralMcmMessage.
I tried replicate your problem.
User control GeneralMcmMessage code behind
public partial class GeneralMcmMessage : UserControl, INotifyPropertyChanged
{
private int _messageId;
public int MessageId
{
get
{
return _messageId;
}
set
{
_messageId = value;
OnPropertyChanged("MessageId");
}
}
private DateTime _timeStamp;
public DateTime TimeStamp
{
get
{
return _timeStamp;
}
set
{
_timeStamp = value;
OnPropertyChanged("TimeStamp");
}
}
public GeneralMcmMessage()
{
InitializeComponent();
//don’t set data context here
//DataContext = this;
}
}
User control GeneralMcmMessage XAML
<StackPanel>
<TextBlock Margin="5" FontSize="20" Text="{Binding Path=MessageId}"/>
<TextBlock Margin="5" FontSize="20" Text="{Binding Path=TimeStamp}"/>
</StackPanel>
User control GeneralMcmMessage usage
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<GeneralMcmMessage> _generalMessages;
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ObservableCollection<GeneralMcmMessage> GeneralMcmMessages
{
get { return _generalMessages; }
set
{
_generalMessages = value;
OnPropertyChanged("GeneralMcmMessages");
}
}
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
{
GeneralMcmMessages = new ObservableCollection<GeneralMcmMessage>();
for (int i = 0; i < 10; i++)
{
var newMsg = new GeneralMcmMessage
{
MessageId = i,
TimeStamp = DateTime.Now,
};
GeneralMcmMessages.Add(newMsg);
}
}
}
User control GeneralMcmMessage usage XAML
<ListBox x:Name="ListBox"
Margin="5"
ItemsSource="{Binding Path=GeneralMcmMessages}">
<ListBox.ItemTemplate>
<DataTemplate DataType="stackoverflow:GeneralMcmMessage">
<stackoverflow:GeneralMcmMessage/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If this not solve your problem could you please post all GeneralMcmMessage?
If you want I can upload sample project for you.
Thank you