I am trying to use a filter for a ListView upon and on initialization, the project is throwing a Null Pointer for the ListView, even though I am setting it in the Constructor and the binding. Here is my code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var viewModel = new MainViewModel();
this.ProjectListView.ItemsSource = viewModel.Projects;
And here is the XAML that refernces it.
<ListView x:Name="ProjectListView" Margin="0,0,10,0" ItemsSource="{Binding Projects}" FontSize="16" Foreground="Black">
I am not sure why the ItemSource is null if it is initialized in the Constructor. Is there a PropertyChanged binding I need to apply?
Here's most of my XAML
<TextBox x:Name="FolderNameBox" Grid.Column="1" Background="White" Grid.Row="1" Grid.ColumnSpan="5"
Margin="0,0,287,654.333" VerticalContentAlignment="Center"
Padding="6" FontSize="16"
IsReadOnly="True"
Text="{Binding ElementName=Hierarchy, Path=SelectedItem.Path, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}">
</TextBox>
<TextBox x:Name="SearchProjectsBox" Grid.Column="5" Background="White" Grid.Row="1" Text="Search Projects"
Margin="47.333,0,0,654.333" VerticalContentAlignment="Center" Foreground="LightGray" Padding="6" FontSize="16" HorizontalAlignment="Left" Width="268" GotFocus="TextBox_GotFocus" LostFocus="TextBox_LostFocus" TextChanged="FilterListView"/>
<TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="226"
ItemsSource="{Binding Path=Projects}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ChildFolders}">
<StackPanel Orientation="Horizontal" >
<Image Source="{Binding Icon}" Margin="5, 5, 5, 5"></Image>
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" BorderThickness="0" FontSize="16" Margin="5" IsReadOnly="True" PreviewMouseDoubleClick="SelectAll" LostFocus="TextBoxLostFocus"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Grid Grid.ColumnSpan="2" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="245,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="540">
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<ListView x:Name="ProjectListView" Margin="0,0,10,0" ItemsSource="{Binding Projects}" FontSize="16" Foreground="Black">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="false">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
ANd heres my base View Model
private ObservableCollection<Project> projects;
public ObservableCollection<Project> Projects
{
get { return projects; }
set
{
if (value != projects)
{
projects = value;
OnPropertyChanged("Projects");
}
}
}
This is the Code Behind
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
MainViewModel model = new MainViewModel();
model.BuildData();
this.ProjectListView.ItemsSource = model.Projects;
}
private void FilterListView(object sender, TextChangedEventArgs e)
{
CollectionViewSource.GetDefaultView(this.ProjectListView.ItemsSource).Refresh();
}
FilterListView is the TextChange method. It breaks on the first line. ProjectListVIew is registering as null but it shouldn't even be launched because it is dependent on a change in the TextBox.
Edit. Here's the code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
MainViewModel model = new MainViewModel();
model.BuildData();
this.ProjectListView.ItemsSource = model.Projects;
}
private void FilterListView(object sender, TextChangedEventArgs e)
{
CollectionViewSource.GetDefaultView(this.ProjectListView.ItemsSource).Refresh();
}
private void SelectAll(object sender, MouseButtonEventArgs e)
{
TextBox box = sender as TextBox;
box.IsReadOnly = false;
box.SelectAll();
}
private void TextBoxLostFocus(object sender, RoutedEventArgs e)
{
TextBox box = sender as TextBox;
box.IsReadOnly = true;
}
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
TextBox tb = (TextBox)sender;
tb.Text = string.Empty;
tb.Foreground = Brushes.Black;
tb.GotFocus -= TextBox_GotFocus;
}
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
TextBox box = (TextBox)sender;
box.Text = "Search Projects";
box.Foreground = Brushes.LightGray;
box.GotFocus += TextBox_GotFocus;
}
}
}
Base View Model
public class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string projectName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(projectName));
}
}
#endregion INotifyPropertyChanged
}
And the View Model I am using
class MainViewModel : ViewModelBase
{
private ObservableCollection<Project> projects;
public ObservableCollection<Project> Projects
{
get { return projects; }
set
{
if (value != projects)
{
projects = value;
OnPropertyChanged("Projects");
}
}
}
EDIT:
My ListView constantly changes - as in, the data shown will be based on a User's selection in a TreeView. Right now, my filter doesn't take into consideration what is actually on the screen. It is only filtering based on the binded Collection.
So if my Collection is Fruit, Vegetables, Meat, Dairy and my ListView is Apple, Carrot, and Milk, if I search Apple, the ListView will be blank but if I search Fruit, I will have a populated row. Not correcty, clearly.
Sounds like I am fairly close.
This is my View Model:
public ICollectionView SourceCollection
{
get
{
return this.projectCollection.View;
}
}
public string FilterText
{
get
{
return filterText;
}
set
{
filterText = value;
this.projectCollection.View.Refresh();
RaisePropertyChanged("SearchProjectsText");
}
}
private void projectCollection_Filter(object sender, FilterEventArgs e)
{
if (string.IsNullOrEmpty(FilterText))
{
e.Accepted = true;
return;
}
Project project = e.Item as Project;
if (project.Name.ToUpper().Contains(FilterText.ToUpper()) || project.Path.ToUpper().Contains(FilterText.ToUpper()))
{
e.Accepted = true;
}
else
{
e.Accepted = false;
}
}
public void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Where SearchProjectText is the actual TextBox used for filtering. My relevant XAML:
<TextBox x:Name="SearchProjectsBox" Grid.Column="5" Background="White" Grid.Row="1" Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}"
Margin="47.333,0,0,654.333" VerticalContentAlignment="Center" Foreground="Black" Padding="6" FontSize="16" HorizontalAlignment="Left" Width="268"/>
<TreeView x:Name="Hierarchy" Grid.Column="4" HorizontalAlignment="Left" Height="631" Margin="0,58,0,0" Grid.Row="1" VerticalAlignment="Top" Width="226"
ItemsSource="{Binding Path=Projects}">
<ListView x:Name="ProjectListView" Margin="0,0,10,0" ItemsSource="{Binding SourceCollection}" FontSize="16" Foreground="Black">
You never set the datacontext. Don't both bind and assign; you don't need both. Just set the DataContext of the parent, and bind the appropriate parent property in the XAML (which you already did -- your XAML looks fine):
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
And make sure your viewmodel properly raises INotifyPropertyChanged.PropertyChanged when you assign a new collection instance to its Projects property.
And make sure you do give Projects a collection, and that there's something in it.
You're using ObservableCollection<T> I hope, not List<T>, for the collection?
Update
I don't know why (it's late and I'm tired, sorry!), but FilterListView is getting called before InitializeComponent(). I should've thought of that when you described the symptoms. This fixes it for me:
private void FilterListView(object sender, TextChangedEventArgs e)
{
if (this.ProjectListView != null)
{
CollectionViewSource.GetDefaultView(this.ProjectListView.ItemsSource).Refresh();
}
}
set the DataContext that will create a new instance of the ViewModel it's binding to.
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
Related
I'm writing a WPF application and my problem is that I have a class member bound with the property "IsEnabled" of one element of xaml and basically the binding seems not to work at all.
Here some extracts of my code:
XAML
<Window.Resources>
<Style TargetType="{x:Type DockPanel}" x:Key="MyDockPanel">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="Gray"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<DockPanel x:Name="home_dock_panel" Style="{StaticResource MyDockPanel}" HorizontalAlignment="Left" Height="35" LastChildFill="False" Margin="22,82,0,0" VerticalAlignment="Top" Width="189" MouseDown="GoToHome" IsEnabled="{Binding Path=UnlockMenu}">
<Image Source="images/home.png" Height="26" RenderTransformOrigin="1.277,0.518" Margin="0,5,0,4.2"/>
<Label Content="Home" Height="35" VerticalAlignment="Top" Foreground="White" FontSize="20" FontWeight="Bold"/>
</DockPanel>
Here the function that allows changing page
private void GoToXPage(object sender, MouseButtonEventArgs e)
{
if(central_frame.Content.GetType() != typeof(LathePage))
{
ViewModel viewModel = new ViewModel();
central_frame.Content = new XPage(viewModel);
panel_dock.DataContext = viewModel;
}
Here a part of ViewModel code
public class ViewModel : INotifyPropertyChanged
{
public GenerateCommand Generate { get; set; }
public bool UnlockMenu { get; set; }
public ViewModel()
{
Generate = new GenerateCommand();
Generate.PropertyChanged += InnerPropertyChanged;
UnlockMenu = Generate.UnlockMenu;
}
private void InnerPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "UnlockMenu")
{
UnlockMenu = Generate.UnlockMenu;
NotifyPropertyChanged("UnlockMenu");
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
In the class GenerateCommand I call NotifyPropertyChanged("UnlockMenu").
Does anyone have some suggestions on this problem?
So i've been trying to build a simple rss feed app for win10 using universal apps and mvvmcross that will do the following:
Load all items from an address in a listbox or something similar which will show them in the splitview.
When clicking on a title it will open a WebView with the full article of the specific title.
The problem I have, is binding different parts of the same RssItems to different controls but keep their relation. I'm new to both technologies but i think it should be possible somehow, I just can't find the way.
These are the related parts of the code I wrote:
viewModel.cs:
class FirstViewModel : MvxViewModel
{
private List<string> _rssItems;
public List<string> RssItems
{
get { return _rssItems; }
set { _rssItems = value; RaisePropertyChanged(() => RssItems); }
}
public MvxCommand SelectionChangedCommand
{
get
{
return new MvxCommand(() =>
{
LoadRssItems();
});
}
}
private async void LoadRssItems()
{
List<string> feedItems = new List<string>();
SyndicationClient rssReaderClient = new SyndicationClient();
SyndicationFeed rssFeed = await rssReaderClient.RetrieveFeedAsync(new Uri("xml address"));
if (rssFeed != null)
{
foreach (var item in rssFeed.Items)
{
feedItems.Add(item.Title.Text);
RssItemsOrinigal.Add(item);
}
RssItems = feedItems;
}
}
And the firstView.xaml:
<views:MvxWindowsPage
x:Class="RssReader.Views.FirstView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:RssReader.Views"
xmlns:views="using:MvvmCross.WindowsUWP.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<RelativePanel>
<Button x:Name="HamburgerButton"
RelativePanel.AlignLeftWithPanel="True"
FontFamily="Segoe MDL2 Assets"
FontSize="36"
Content=""
Click="HamburgerButton_Click"/>
<SplitView Grid.Row="1"
x:Name="sv"
DisplayMode="CompactOverlay"
OpenPaneLength="200"
CompactPaneLength="50">
<SplitView.Pane>
<ListBox x:Name="lstMenuItems" SelectionMode="Single">
<ListBoxItem x:Name="ListBoxItem1">
<StackPanel Orientation="Horizontal">
<Button FontFamily="Segoe MDL2 Assets" FontSize="36" Command="{Binding SelectionChangedCommand}"></Button>
<TextBlock FontSize="24" Text="Website 1" />
</StackPanel>
</ListBoxItem>
</ListBox>
</SplitView.Pane>
<SplitView.Content>
<ListBox ItemsSource="{Binding RssItems}"/>
</SplitView.Content>
</SplitView>
</Grid>
</views:MvxWindowsPage>
One way to do this is using ContentPresenter together with your ListBox and set the Visibility property each time when you select an item or want to go back to the ListBox:
<Page.Resources>
<DataTemplate x:Key="DetailContent">
<WebView Source="{Binding DetailUri}" />
</DataTemplate>
<DataTemplate x:Key="ListBoxContent">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</Page.Resources>
...
<SplitView.Content>
<Grid>
<ContentPresenter Content="{x:Bind listBox.SelectedItem, Mode=OneWay}"
ContentTemplate="{StaticResource DetailContent}" Visibility="{x:Bind VM.IsVisible, Mode=OneWay}" />
<ListBox x:Name="listBox" ItemsSource="{x:Bind VM.RssItems}"
ItemTemplate="{StaticResource ListBoxContent}" SelectionChanged="{x:Bind VM.listBox_SelectionChanged}" />
<Button Content="Close and Show ListBox" VerticalAlignment="Bottom" Visibility="{x:Bind VM.IsVisible, Mode=OneWay}"
Click="{x:Bind VM.IsVisible_Clicked}" />
</Grid>
</SplitView.Content>
In the ViewModel of this page for example:
public class Page6ViewModel : INotifyPropertyChanged
{
public ObservableCollection<ListBoxDetail> RssItems;
private Visibility _IsVisible = Visibility.Collapsed;
public Visibility IsVisible
{
get { return _IsVisible; }
set
{
if (value != _IsVisible)
{
_IsVisible = value;
OnpropertyChanged();
}
}
}
public Page6ViewModel()
{
RssItems = new ObservableCollection<ListBoxDetail>();
RssItems.Add(new ListBoxDetail { Title = "Item 1", DetailUri = "http://stackoverflow.com/questions/38853708/using-different-parts-of-rss-feed-in-a-universal-app-using-mvvmcross-and-command?noredirect=1#comment65137456_38853708" });
RssItems.Add(new ListBoxDetail { Title = "Item 2", DetailUri = "https://msdn.microsoft.com/en-us/library/windows/apps/ms668604(v=vs.105).aspx" });
RssItems.Add(new ListBoxDetail { Title = "Item 3", DetailUri = "https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.controls.itemscontrol.itemssource.aspx" });
}
private ListBox listBox;
public void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
listBox = sender as ListBox;
listBox.Visibility = Visibility.Collapsed;
IsVisible = Visibility.Visible;
}
public void IsVisible_Clicked(object sender, RoutedEventArgs e)
{
listBox.Visibility = Visibility.Visible;
IsVisible = Visibility.Collapsed;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnpropertyChanged([CallerMemberName]string propertyName = "")
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Another common method is using Frame as Content of SpiltView, then create a page for ListBox and a page for WebView, at first navigate this frame to the page of ListBox, in the SelectionChanged event, you can send the uri as parameter and navigate this frame to the page of WebView. For this method, there are many samples on the internet and you can search for them.
I have a datagrid in WPF bound to an ObservableCollection wich I read/save in a XML File. I'd now like to add a comboboxcolumn with checkboxes or something similar.
There should be a dropdown menu for selecting one or more weekdays.
Does anyone can help me out?
Thanks in advance!
Edit:
After implementing christoph's custom control(DropDownDayPicker), I could bind my data to it, but not in the other way(get the updated value(s) if it changes)
So here's what I tried:
My Object:
Entry.cs
public class Entry : INotifyPropertyChanged
{
string Id;
string ExecuteOn;
}
MyWindow.xaml.cs:
public ObservableCollection<Entry> entryList;
doc.Load("C:\\test\\list.xml");
XmlElement root = doc.DocumentElement;
XmlNodeList nodes = root.SelectNodes("Entry");
foreach(XmlNode node in nodes)
{
XmlNodeList subnodes = node.SelectNodes("ExecuteOn");
ObservableCollection<Weekday> days = new ObservableCollection<Weekday>();
foreach(XmlNode subnode in node["ExecuteOn"].ChildNodes)
days.Add( (Weekday)Enum.Parse(typeof(Weekday),subnode.InnerText));
_entryList.Add(new Entry(
node["Id"].InnerText,
node["Description"].InnerText,
node["Path"].InnerText,
Convert.ToInt32(node["KindOfTask"].InnerText),
days
));
}
MyWindow.xaml
<DataGrid x:Name="EntryView" ItemsControl.ItemsSource="{Binding EntryList}" DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
AutoGenerateColumns="false" Margin="0,34,0,37" CanUserAddRows="false" Height="Auto">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Days" x:Name="cellExecuteOn" Width="*" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:DropDownDayPicker SelectedWeekdays="{Binding ExecuteOn}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Here's what the xml looks like:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfEntry>
<Entry>
<Id>efbae4da-f833-4d07-a8af-9ec3421b4886</Id>
<ExecuteOn>
<Weekday>Montag</Weekday>
<Weekday>Dienstag</Weekday>
</ExecuteOn>
</Entry>
<Entry>
<Id>1cb13340-40dd-48c1-ada5-bbb6f79c0d06</Id>
<ExecuteOn>
<Weekday>Montag</Weekday>
</ExecuteOn>
</Entry>
</ArrayOfEntry>
You can use DataGridTemplateColumn. Try something like this:
<DataGrid ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="Monday"/>
<CheckBox Content="Friday"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Hope that suits your needs.
Update:
Ok, now the professional approach. I made a custom control for what you want. (Please don´t rate the look, you can customize it). Here comes the code...
Weekday.cs
public enum Weekday
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
DropDownDayPicker.cs
public class DropDownDayPicker : Control
{
private List<CheckBox> checkboxes = new List<CheckBox>();
static DropDownDayPicker()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DropDownDayPicker), new FrameworkPropertyMetadata(typeof(DropDownDayPicker)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
StackPanel weekdayBoxes = this.GetTemplateChild("PART_weekdayHost") as StackPanel;
foreach(CheckBox box in weekdayBoxes.Children)
{
box.Checked += Box_CheckedChanged;
box.Unchecked += Box_CheckedChanged;
this.checkboxes.Add(box);
}
Button openPopup = this.GetTemplateChild("PART_openPopupButton") as Button;
openPopup.Click += OpenPopup_Click;
this.UpdateCheckboxes();
}
private void OpenPopup_Click(object sender, RoutedEventArgs e)
{
Popup popup = this.GetTemplateChild("PART_popup") as Popup;
popup.IsOpen = !popup.IsOpen;
}
private void Box_CheckedChanged(object sender, RoutedEventArgs e)
{
this.UpdateSelectedWeekdays();
}
public ObservableCollection<Weekday> SelectedWeekdays
{
get { return (ObservableCollection<Weekday>)GetValue(SelectedWeekdaysProperty); }
set { SetValue(SelectedWeekdaysProperty, value); }
}
public static readonly DependencyProperty SelectedWeekdaysProperty =
DependencyProperty.Register("SelectedWeekdays", typeof(ObservableCollection<Weekday>), typeof(DropDownDayPicker), new PropertyMetadata(new ObservableCollection<Weekday>(), SelectedWeekdaysPropertyChanged));
private static void SelectedWeekdaysPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
DropDownDayPicker picker = sender as DropDownDayPicker;
ObservableCollection<Weekday> oldValue = args.OldValue as ObservableCollection<Weekday>;
ObservableCollection<Weekday> newValue = args.NewValue as ObservableCollection<Weekday>;
if (picker != null)
{
if (oldValue != null)
{
oldValue.CollectionChanged -= picker.SelectedWeekdaysChanged;
}
if (newValue != null)
{
newValue.CollectionChanged += picker.SelectedWeekdaysChanged;
}
picker.UpdateCheckboxes();
}
}
private void SelectedWeekdaysChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
this.UpdateCheckboxes();
}
private bool updating = false;
private void UpdateCheckboxes()
{
if (!this.updating)
{
this.updating = true;
if (this.SelectedWeekdays != null)
{
foreach (CheckBox box in this.checkboxes)
{
box.IsChecked = this.SelectedWeekdays.Contains((Weekday)box.Tag);
}
}
this.UpdateSummary();
this.updating = false;
}
}
private void UpdateSelectedWeekdays()
{
if (!this.updating)
{
this.updating = true;
var selectedWeekdays = this.checkboxes.Where(x => x.IsChecked.HasValue && x.IsChecked.Value).Select(x => x.Tag).Cast<Weekday>();
this.SelectedWeekdays = new ObservableCollection<Weekday>(selectedWeekdays);
this.UpdateSummary();
this.updating = false;
}
}
private void UpdateSummary()
{
TextBlock summary = this.GetTemplateChild("PART_summary") as TextBlock;
if (this.SelectedWeekdays != null)
{
if (this.SelectedWeekdays.Count == 0)
{
summary.Text = "none";
}
else if (this.SelectedWeekdays.Count == 1)
{
summary.Text = this.SelectedWeekdays[0].ToString();
}
else if (this.SelectedWeekdays.Count > 1)
{
summary.Text = string.Format("{0} days",this.SelectedWeekdays.Count);
}
}
else
{
summary.Text = "none";
}
}
}
in Generic.xaml
<Style TargetType="{x:Type local:DropDownDayPicker}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Button x:Name="PART_openPopupButton">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Margin="3" x:Name="PART_summary"/>
<TextBlock FontFamily="Segoe UI Symbol" Text="" Grid.Column="1" FontWeight="Bold"/>
</Grid>
</Button>
<Popup PlacementTarget="{Binding ElementName=PART_openPopupButton}" IsOpen="False" x:Name="PART_popup" StaysOpen="False">
<StackPanel x:Name="PART_weekdayHost" Background="White">
<CheckBox Content="Monday">
<CheckBox.Tag>
<local:Weekday>Monday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Thusday">
<CheckBox.Tag>
<local:Weekday>Tuesday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Wednesday">
<CheckBox.Tag>
<local:Weekday>Wednesday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Thursday">
<CheckBox.Tag>
<local:Weekday>Thursday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Friday">
<CheckBox.Tag>
<local:Weekday>Friday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Saturday">
<CheckBox.Tag>
<local:Weekday>Saturday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
<CheckBox Content="Sunday">
<CheckBox.Tag>
<local:Weekday>Sunday</local:Weekday>
</CheckBox.Tag>
</CheckBox>
</StackPanel>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Width" Value="150"/>
</Style>
and the datagrid
<DataGrid ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:DropDownDayPicker SelectedWeekdays="{Binding whatEver}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I know this is pretty much effort but that is the way I would do it in a real world scenario.
Update 2:
I tried making a sample for you and noticed there is a small mistake I made. Please see DropDownDayPicker.cs and update method UpdateSelectedWeekdays like this:
private void UpdateSelectedWeekdays()
{
if (!this.updating)
{
this.updating = true;
var selectedWeekdays = this.checkboxes.Where(x => x.IsChecked.HasValue && x.IsChecked.Value).Select(x => x.Tag).Cast<Weekday>();
SetCurrentValue(DropDownDayPicker.SelectedWeekdaysProperty, new ObservableCollection<Weekday>(selectedWeekdays));
BindingExpression binding = this.GetBindingExpression(DropDownDayPicker.SelectedWeekdaysProperty);
if (binding != null)
{
binding.UpdateSource();
}
this.UpdateSummary();
this.updating = false;
}
}
So with that corrected lets come to the sample code.
Entry.cs
public class Entry : INotifyPropertyChanged
{
private string title;
public string Title
{
get { return title; }
set { title = value; this.OnPropertyChanged("Title"); }
}
private ObservableCollection<Weekday> days = new ObservableCollection<Weekday>();
public ObservableCollection<Weekday> Days
{
get { return days; }
set { days = value; this.OnPropertyChanged("Days"); }
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Viewmodel.cs
public class Viewmodel
{
private ObservableCollection<Entry> collection = new ObservableCollection<Entry>()
{
new Entry() { Title = "Entry 1" },
new Entry() { Title = "Entry 2" },
new Entry() { Title = "Entry 3" }
};
public ObservableCollection<Entry> Collection
{
get { return collection; }
set { collection = value; }
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:Viewmodel/>
</Window.DataContext>
<StackPanel>
<Button Content="Click" Click="Button_Click_1"/>
<DataGrid ItemsSource="{Binding Collection}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Title}" Header="Title"/>
<DataGridTemplateColumn Header="Days">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:DropDownDayPicker SelectedWeekdays="{Binding Days, Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<TextBlock Text="{Binding Collection[0].Days.Count}"/>
</StackPanel>
</Window>
Now when changing the days of the first row you see the count of selected days beneath the datagrid. Notice that the default binding mode of the selected days is OneWay. You have to assign Mode=TwoWay in order to make it working.
Try it out and give me some feedback.
Similar to what christoph had you can implement a combobox with something like this
<DataGrid ItemsSource="{Binding SomeCollection}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Id">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Id}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Week Days">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Days}"/>
<CheckBox/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Where Days is an enum property in a ObservableCollection of that class
public class SomeData
{
public int Id { get; set; }
public Days Days { get; set; }
}
public enum Days
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public ObservableCollection<SomeData> SomeCollection { get; set; }
}
Here is a good resource to learn about wpf datagrids
Datagrid tutorial
I am developing my first Windows 8 app, in one page i am trying to update button text with latest timestop when page loads. I defined my xaml and codebehind like below:
I am using databinding to update the button text but it is not working as expected:
MainPage.xaml
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Button HorizontalAlignment="Left" Margin="333,284,0,0" VerticalAlignment="Top" Height="69" Width="162">
<Button.Resources>
<DataTemplate x:Key="DataTemplate1">
<Grid>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding ButtonText}" VerticalAlignment="Top" Foreground="#FFFF6800" Height="34" Margin="-30,0,-22,-14" Width="115"/>
</Grid>
</DataTemplate>
</Button.Resources>
<Button.ContentTemplate>
<StaticResource ResourceKey="DataTemplate1"/>
</Button.ContentTemplate>
</Button>
</Grid>
MainPage.xaml.cs
public StatsClass Stats { get; private set; }
public MainPage()
{
this.InitializeComponent();
this.DataContext = Stats;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
UpdateButton();
}
private void UpdateButton()
{
if (Stats == null)
Stats = new StatsClass();
Stats.ButtonText = DateTime.Now.ToString();
}
StatsClass.cs
public class StatsClass : INotifyPropertyChanged
{
private string _buttonText;
public string ButtonText
{
get
{
return _buttonText;
}
set
{
_buttonText = value;
OnPropertyChanged("ButtonText");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
You have set Content of your Button twice, once with Content="Button" and again with.Button.ContentTemplate. You could just have:
<Button HorizontalAlignment="Left" Margin="333,284,0,0" VerticalAlignment="Top" Height="69" Width="162">
<Grid>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding ButtonText}" VerticalAlignment="Top" Foreground="#FFFF6800" Height="34" Margin="-30,0,-22,-14" Width="115"/>
</Grid>
</Button>
I had a similar issue yesterday using binding in a DataTemplate. I guess that you also had a binding error in the debug output.
I solved it using a relative source like that:
<TextBlock Text={Binding DataContext.ButtonText,
RelativeSource={RelativeSource FindAncestor, AncestorType=*YourControl*}}"/>
The Template has no direct access to the datacontext. By using the relative source you can bind to its properties.
In this program, I'm able to add one tab at a time by clicking the "Add Course" button. Ideally, the header of the tab should be the course name I entered and the text in the textbox , which is on the tab, should display the course name.
However, it's not functioning correctly. When I tried to add more than 1 tabs, each time it gives me this error message:
System.Windows.Data Error: 40 : BindingExpression path error: 'Text' property not found on 'object' ''MyHomeworkViewModel' (HashCode=33010577)'. BindingExpression:Path=Text; DataItem='MyHomeworkViewModel' (HashCode=33010577); target element is 'TextBox' (Name=''); target property is 'Text' (type 'String')
Also, it seems to "override" other tab's text (just text, not header). For example, if I add a tab with header "a", the text of that is also "a". Then if I add "B", both textboxes on two tabs become "B". However, if I print out the Text property of each tab (MyHomeworkModel in this case), they are "a" and "B", respectively.
I have been debugging this whole day but no luck. Any help would be appreciated!
My View (DataContext set to MyHomeworkViewModel):
<Window x:Class="MyHomework__MVVM_.MyHomeworkView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="My Homework" Height="450" Width="800" ResizeMode="CanMinimize">
<Grid Margin="0,0,10,10">
<TabControl HorizontalAlignment="Left" Height="330" VerticalAlignment="Top" Width="764" Margin="10,10,0,0" ItemsSource="{Binding AllTabs}" SelectedItem="{Binding SelectedTab}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Content">
<Setter.Value>
<Grid>
<TextBox Text="{Binding Text}" FontSize="16" AcceptsReturn="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</TextBox>
</Grid>
</Setter.Value>
</Setter>
<Setter Property="FontSize" Value="20"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
<Button Content="Add Course" HorizontalAlignment="Left" VerticalAlignment="Top" Width="105" Margin="10,351,0,0" Height="50" Command="{Binding AddCourseCommand}"/>
<Button Content="Drop Course" HorizontalAlignment="Left" VerticalAlignment="Top" Width="76" Margin="138,379,0,0" Height="22" Command="{Binding DropCourseCommand, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="Save HW" HorizontalAlignment="Left" VerticalAlignment="Top" Width="105" Margin="669,351,0,0" Height="50"/>
</Grid>
</Window>
My Model:
using System.ComponentModel;
namespace MyHomework__MVVM_
{
class MyHomeworkModel : INotifyPropertyChanged
{
private string header, text;
public event PropertyChangedEventHandler PropertyChanged;
public string Header
{
get
{
return header;
}
set
{
header = value;
OnPropertyChanged("Header");
}
}
public string Text
{
get
{
return text;
}
set
{
text = value;
OnPropertyChanged("Text");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
My ViewModel:
using MyHomework;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
namespace MyHomework__MVVM_
{
class MyHomeworkViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyHomeworkModel> allTabs;
private MyHomeworkModel selectedTab;
private MyHomeworkView mainWindow;
public event PropertyChangedEventHandler PropertyChanged;
public MyHomeworkViewModel(MyHomeworkView mainWindow)
{
allTabs = new ObservableCollection<MyHomeworkModel>();
this.mainWindow = mainWindow;
AddCourseCommand = new AddCourseCommand(this);
DropCourseCommand = new DropCourseCommand(this);
}
public ObservableCollection<MyHomeworkModel> AllTabs
{
get
{
return allTabs;
}
set
{
allTabs = value;
OnPropertyChanged("AllTabs");
}
}
public MyHomeworkModel SelectedTab
{
get
{
return selectedTab;
}
set
{
selectedTab = value;
OnPropertyChanged("SelectedTab");
}
}
public ICommand AddCourseCommand
{
get;
private set;
}
public ICommand DropCourseCommand
{
get;
private set;
}
public void AddNewTab()
{
NewCourseName ncn = new NewCourseName();
ncn.Owner = mainWindow;
ncn.ShowDialog();
if (ncn.courseName != null)
{
MyHomeworkModel newTab = new MyHomeworkModel();
newTab.Header = ncn.courseName;
newTab.Text = ncn.courseName;
AllTabs.Add(newTab);
SelectedTab = newTab;
}
foreach (MyHomeworkModel item in AllTabs)
{
Console.WriteLine(item.Text);
}
}
public bool CanDrop()
{
return SelectedTab != null;
}
public void RemoveTab()
{
DropCourseConfirmation dcc = new DropCourseConfirmation();
dcc.Owner = mainWindow;
dcc.ShowDialog();
if (dcc.drop == true)
{
int index = AllTabs.IndexOf(SelectedTab);
AllTabs.Remove(SelectedTab);
if (AllTabs.Count > 0)
{
if (index == 0)
{
SelectedTab = AllTabs[0];
}
else
{
SelectedTab = AllTabs[--index];
}
}
else
{
SelectedTab = null;
}
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Please let me know if you need more codes to help me.
Change your
<Setter Property="Content">
<Setter.Value>
<Grid>
<TextBox Text="{Binding Text}" FontSize="16" AcceptsReturn="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</TextBox>
</Grid>
</Setter.Value>
</Setter>
for this:
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<TextBox Text="{Binding Text}" FontSize="16" AcceptsReturn="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
</TextBox>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
Your DataContext isn't what you think it is. Read the error there. It states that "Text" is not a valid property on MyHomeworkViewModel which is true (As opposed to your MyHomeworkModel).
What you need to be modifying instead of the ItemContainerStyle is instead the ItemTemplate and the ContentTemplate which uses the appropriate object within your ItemsSource as its DataContext.
Additionally, the binding in your TextBox needs to be Text="{Binding Text, Mode=TwoWay}" or it won't modify the property in your model.