I am trying to understand material design in xaml in WPF and at this moment I am working on Dialog Host. I tried to put UserControl in material design dialog host but somehow it is not working, I am using material design with Caliburn Micro FW.
MainView.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--ROW 0-->
<Button Content="Show Dialog" Grid.Row="0" Grid.Column="0" x:Name="OpenDialog"/>
<!--ROW 1-->
<materialDesign:DialogHost Grid.Row="1" Grid.Column="0" IsOpen="{Binding IsDialogOpen}" CloseOnClickAway="True">
<materialDesign:DialogHost.DialogContent>
<StackPanel Margin="20">
<ContentControl x:Name="ActiveItem"/>
</StackPanel>
</materialDesign:DialogHost.DialogContent>
</materialDesign:DialogHost>
</Grid>
MainViewModel.cs
private bool _isDialogOpen;
public bool IsDialogOpen
{
get { return _isDialogOpen; }
set
{
_isDialogOpen = value;
NotifyOfPropertyChange(() => IsDialogOpen);
}
}
public LoginViewModel()
{
MyCustomers.Add("Dhairya Joshi");
MyCustomers.Add("Irfan Shethia");
MyCustomers.Add("Imran Shethia");
}
public void OpenDialog()
{
IsDialogOpen = true;
ActivateItem(new CustomersListViewModel());
}
CustomerView.xaml
<StackPanel Orientation="Vertical">
<ListBox x:Name="MyCustomers"/>
</StackPanel>
CustomerViewModel.cs
private List<string> _myCustomers = new List<string>();
public List<string> MyCustomers
{
get { return _myCustomers; }
set {
_myCustomers = value;
NotifyOfPropertyChange(() => MyCustomers);
}
}
public CustomersListViewModel()
{
MyCustomers.Add("Dhairya Joshi");
MyCustomers.Add("Irfan Shethia");
MyCustomers.Add("Imran Shethia");
}
Right now it is just showing like this screen shot
.
NOTE : I tried to put the <contentControl> in new row and same code does works fine but it is just not showing when i am using it inside DialogHost. Tried to remove StackPanel also and left only with <contentcontrol> but still its not working.
Update:
Finally I was able to put it in working condition but again another problem arises is that ListBox which is inside User Control is not getting populated.
How I was able to show User Control in Dialog:
1) MainView.xaml
<materialDesign:DialogHost Grid.Row="2" Grid.Column="0" IsOpen="{Binding IsDialogOpen}" CloseOnClickAway="True" Identifier="RootDialog">
2) MainViewModel.cs
public async void OpenDialog()
{
//IsDialogOpen = true;
var view = new CustomersListView
{
DataContext = new CustomerListViewModel();
};
//show the dialog
var result = await DialogHost.Show(view, "RootDialog", ExtendedOpenedEventHandler, ExtendedClosingEventHandler);
}
private void ExtendedOpenedEventHandler(object sender, DialogOpenedEventArgs eventargs)
{
Console.WriteLine("Detecting Opened Event");
}
private void ExtendedClosingEventHandler(object sender, DialogClosingEventArgs eventArgs)
{
}
If i run the usercontrol directly then listview populates but when i do it inside dialog it does not. Not sure why.
it's work very good
https://github.com/bebenins/DialogRefreshIssue
good lock
Related
I am trying to get from my database one item, and when clicking on the next or previous button, I would like to get the next item out of my database by increasing its ID. I'm at the point of having my first item in my card, but when I click on previous or next, nothing happens.
I have in xaml:
<smtx:XamlDisplay Key="cards_1" Margin="4 4 0 0">
<materialDesign:Flipper Style="{StaticResource MaterialDesignCardFlipper}">
<materialDesign:Flipper.FrontContent>
<Grid Height="350" Width="200">
<Grid.RowDefinitions>
<RowDefinition Height="250" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<materialDesign:ColorZone Mode="PrimaryMid" VerticalAlignment="Stretch">
<materialDesign:PackIcon Kind="AccountCircle" Height="128" Width="128"
VerticalAlignment="Center" HorizontalAlignment="Center" />
</materialDesign:ColorZone>
<StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{Binding CurrentGebruiker.Naam}"></TextBlock>
<Button Style="{StaticResource MaterialDesignFlatButton}" Foreground="DarkGoldenrod"
Command="{x:Static materialDesign:Flipper.FlipCommand}"
Margin="0 4 0 0"
>SHOW DETAILS</Button>
</StackPanel>
</Grid>
</materialDesign:Flipper.FrontContent>
And my viewmodel:
public ZoekMatchViewModel()
{
LeesGebruiker(1);
KoppelenCommands();
}
private Gebruiker currentGebruiker;
public Gebruiker CurrentGebruiker
{
get
{
return currentGebruiker;
}
set
{
currentGebruiker = value;
NotifyPropertyChanged();
}
}
private void KoppelenCommands()
{
NextCommand = new BaseCommand(VolgendeGebruiker);
PrevCommand = new BaseCommand(VorigeGebruiker);
}
public ICommand NextCommand { get; set; }
public ICommand PrevCommand { get; set; }
private void LeesGebruiker(int id)
{
//instantiƫren dataservice
ZoekMatchDataService zoekMatchDS =
new ZoekMatchDataService();
currentGebruiker = zoekMatchDS.GetGebruiker(id);
}
public void VolgendeGebruiker()
{
if (CurrentGebruiker != null)
{
int id = (currentGebruiker.ID) + 1;
LeesGebruiker(id);
}
}
public void VorigeGebruiker()
{
if (CurrentGebruiker != null)
{
int id = (currentGebruiker.ID) - 1;
LeesGebruiker(id);
}
}
My buttons:
<Button Command="{Binding PrevCommand}" Background="Transparent" BorderThickness="0" Height="50">
and
<Button Command="{Binding NextCommand}" Background="Transparent" BorderThickness="0" Height="50">
So the problem is that my xaml doesn't update to the new user when I click on next or previous buttons.
If you need more information, I'm happy to provide!
I think the "right" way to do this would be with a custom Selector. Selector is the base class used in WPF for a control that lets the user select one or more items from a list. For example: ComboBox and ListBox are both Selectors. If you've never made a custom control before this may be a bit complicated though.
There are a few simpler answers. You could make a class that holds all the data you want to display for a single option, and have your Window define a property of that type. Then you could bind all the variable parts of your interface to that property and just change the property value manually when the user clicks the left or right button. Of course you'd only be able to use this on one place, whereas if you made a custom control you could reuse it anywhere, any number of times.
I have a textbox and a datagrid like so:
<Page
TextElement.FontSize="14" FontFamily="Segoe UI"
Title="Delivery View">
<Page.Resources>
<xcdg:DataGridCollectionViewSource x:Key="firstNameDataSource"
Source="{Binding Path=Accessor.Views[FirstNameView].SourceCollection}"
AutoFilterMode="And"
DistinctValuesConstraint="Filtered">
<xcdg:DataGridCollectionViewSource.ItemProperties>
<xcdg:DataGridItemProperty Name="FirstName" CalculateDistinctValues="False"/>
</xcdg:DataGridCollectionViewSource.ItemProperties>
</xcdg:DataGridCollectionViewSource>
</Page.Resources>
<ScrollViewer Name="pendingScroll" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<DockPanel Name="pnlMainPanel" LastChildFill="True" Style="{StaticResource panelBackground}">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" FontSize="18" Text="Pending Guests" Margin="0,1,3,1" Foreground="SteelBlue" HorizontalAlignment="Left"/>
<TextBox Name="txtFirstNameFilter" Grid.Row="1" >
</TextBox>
<xcdg:DataGridControl x:Name="gridPendingGuests" Margin="5,0,5,1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
MinHeight="100"
MinWidth="200"
CellEditorDisplayConditions="None"
EditTriggers="None"
ItemScrollingBehavior="Immediate"
AutoCreateColumns="False"
SelectionMode="Single"
NavigationBehavior="RowOnly"
ItemsSource="{Binding Source={StaticResource firstNameDataSource}}">
<xcdg:DataGridControl.View>
<xcdg:TableView ShowRowSelectorPane="False"/>
</xcdg:DataGridControl.View>
<xcdg:DataGridControl.Columns>
<xcdg:Column x:Name="FirstName" FieldName="FirstName" Title="First Name" Width="150" />
</xcdg:DataGridControl.Columns>
<i:Interaction.Behaviors>
<utils:UpdateDataGridOnTextboxChange/>
</i:Interaction.Behaviors>
</xcdg:DataGridControl>
</Grid>
</DockPanel>
</ScrollViewer>
</Page>
In the datagrid, you have a collection of first names. This works perfectly. The display is good. As you can see, I added an Interactions.Behavior class which currently handles a filter with a hard coded value when the user clicks on the datagrid with their mouse. The filtering works fine. If there is a first name of "John", that record is removed from view, leaving all other records in place.
Here is that code:
using System.Windows.Interactivity;
using System.Windows;
using Xceed.Wpf.DataGrid;
using System;
namespace Some.Namespace.Behaviors
{
public class UpdateDataGridOnTextboxChange : Behavior<DataGridControl>
{
protected override void OnAttached()
{
AssociatedObject.MouseUp += AssociatedObjectOnMouseUp;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.MouseUp -= AssociatedObjectOnMouseUp;
base.OnDetaching();
}
private void AssociatedObjectOnMouseUp(object sender, RoutedEventArgs routedEventArgs)
{
var items = AssociatedObject.Items;
items.Filter = CollectionFilter;
}
private bool CollectionFilter(object item)
{
System.Data.DataRow dr = item as System.Data.DataRow;
//set the ItemArray as Guest
Guest guest = SetGuest(dr);
if (guest.FirstName.Equals("John"))
{
return false;
}
return true;
}
private Guest SetGuest(System.Data.DataRow dr)
{
Guest guest = new Guest();
guest.FirstName = dr.ItemArray[0].ToString();
return guest;
}
public class Guest
{
public string FirstName { get; set; }
}
}
}
This works as expected. Again, when the user clicks on the datagrid, the filter filters out the users with the First Name of "John".
What I WANT to have happen is for the user to be able to type a first name in the txtFirstNameFilter Textbox and the datagrid to then filter the records that contain the text in the first name, keeping them visible and the others without that first name to not be visible.
The way I can do it is with an attached property of the Textbox TextChanged property? That's a question, because I don't know how to do an attached property and then how to make sure that when that attached property actually changes, call the AssociatedObjectOnMouseUp method to run the filtering.
System.Windows.Interactivity.Behavior<T> inherits from DependencyObject. So give it a dependency property and bind that.
public class UpdateDataGridOnTextboxChange : Behavior<DataGrid>
{
#region FilterValue Property
public String FilterValue
{
get { return (String)GetValue(FilterValueProperty); }
set { SetValue(FilterValueProperty, value); }
}
public static readonly DependencyProperty FilterValueProperty =
DependencyProperty.Register(nameof(FilterValue), typeof(String), typeof(UpdateDataGridOnTextboxChange),
new FrameworkPropertyMetadata(null, FilterValue_PropertyChanged));
protected static void FilterValue_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as UpdateDataGridOnTextboxChange).OnFilterValueChanged(e.OldValue);
}
private void OnFilterValueChanged(object oldValue)
{
// Do whatever you do to update the filter
// I did a trace just for testing.
System.Diagnostics.Trace.WriteLine($"Filter value changed from '{oldValue}' to '{FilterValue}'");
}
#endregion FilterValue Property
/*****************************************
All your code here
*****************************************/
}
XAML:
<i:Interaction.Behaviors>
<utils:UpdateDataGridOnTextboxChange
FilterValue="{Binding Text, ElementName=txtFirstNameFilter}"
/>
</i:Interaction.Behaviors>
You should rename it, though. It's got nothing to do with text boxes. You could bind FilterValue to a viewmodel property, or the selected value in a ComboBox, or whatever.
Update
OP's having trouble with the binding only updating FilterValue when the text box loses focus. This isn't what I'm seeing, but I don't know what's different between the two.
There isn't any UpdateTargetTrigger property of Binding, but you can swap the source and the target when both are dependency properties of dependency objects. This works for me:
<TextBox
x:Name="txtFirstNameFilter"
Text="{Binding FilterValue, ElementName=DataGridFilterThing, UpdateSourceTrigger=PropertyChanged}"
/>
<!-- snip snip snip -->
<i:Interaction.Behaviors>
<local:UpdateDataGridOnTextboxChange
x:Name="DataGridFilterThing"
/>
</i:Interaction.Behaviors>
UPDATE 3
" I want that the text from this TextBox should be shown in another TextBox(in another view)"
The second textbox in another view is meant to show other information that is tied to the first textbox, but not the copy.
So the user control contains a text box for, say, Bus code. Once I enter bus code, tabbing out will trigger a fetch from the database for other details such as bus name, bus destination, bus model etc.
The others textbox which is in another view then displays the bus name. All following textboxes display destination and so forth. When the command is invoked, and I try to write to the property BusName, it gets assigned (and I call Notify("BusName")) but it does not show on the UI.
Hope that was more clear. Sorry for any confusion caused :).
UPDATE2 - Response to blindmeis
Thanks for your reply though this not appear to be what I was looking for. The tab out is essential because that is how management wants their pages to be populated i.e. when you tab out of a 'code' textbox after entering the code, it will use the code to fetch data from the db to populate the rest of the controls. This does not appear to have the tab-out behavior in it. As for the 3rd dependency property, it is in my original code, I simply did not include it here because the value in the first textbox (user control tabout textbox) is not relevant to the problem. Simply, what I am trying to accomplish is that the second textbox must populate when you tab-out of the first textbox.
I could do this with an eventhandler, but wanted to use commands. I am thinking now perhaps commands are not the way to go here and I should switch to using an event handler.
Please advise if you still have any ideas on how to get the second textbox to populate when you tab out of the first (by putting a breakpoint in populate, you will see that the property gets assigned. ). If I have not understood correctly or missed something here, please let me know. Thanks!
UPDATE!
I have created a VS2013 solution mimicking my code, which reproduces the problem. It is at this public google drive link as a zip file (takes a few seconds for the download icon to appear):
https://drive.google.com/file/d/0B89vOvsI7Ubdbk85SVlvT3U2dVU/view?usp=sharing
You will see that the 2nd text box does not update despite the bound property storing the new value.
Greatly appreciate any help. Thanks in advance.
Original post:
I have a textbox control to which I have tied a key binding based command to go process some actions (in a method that the command has been tied to) when the user hits tab while in the textbox (tabs out).
I have other controls in that page that are boiund to properties in the viewmodel that I write to in that tab-out connected function. When I write my properties in the constructor or somewhere 'outside' that command invokation they seem to work fine and the values show on the page, but when I write them within that command invocation, the properties in the vm contain the values but don't show up on the UI
Any ideas why and how to fix?
Thanks much in advance
From XAML:
<TextBox Name="txtCode" Text="{Binding Path=CodeValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Tab" Command="{Binding RetrieveRecordCmd}" > </KeyBinding>
</TextBox.InputBindings>
</TextBox>
From VM:
RetrieveRecordCmd = new GSSCommand(RetrieveRecord, param => this.CanExecuteRetrieveRecordCmd);
Command tied function:
public void RetrieveRecord(object obj)
{
objPie = null;
//Check if a record exists for that code
gssSvcMethodStatusBase = gssSvcClientBase.ReadPies(ref gssSvcGlobalVarsBase, out objPie, out grfaBase, CodeValue);
if ((objPie != null)) // && (objPie.DateCreated > DateTime.MinValue))
PopulatePage(objPie);
else if (objPie == null)
InitiateCreateNew();
else
return;
}
It looks like you have implemented the INotifyPropertyChanged interface in the strict sense, but are missing the actual functionality. The interface itself doesn't automatically give you change notifications. You also need to fire the PropertyChanged event when each property changes. The standard pattern looks like:
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
return;
_name = value;
NotifyPropertyChanged("Name");
}
}
You should make a habit of writing all mutable properties which you intend to bind to the UI in this format. Snippets can make this easier to do consistently.
this works, but i dont know if this is the behavior you want.
<UserControl x:Class="ProblemDemoWPF.TextBoxTabOutUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Name="TabOutTextUserControl"
>
<StackPanel Margin="0,0,0,0" Orientation="Horizontal">
<Label Content="UserControl (ucTextBox)->"></Label>
<TextBox Width="80" Height="30" BorderBrush="Black"
BorderThickness="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding ElementName=TabOutTextUserControl, Path=CodeValue}">
</TextBox>
</StackPanel>
</UserControl>
new Dependency Propertie with right binding without DataContext
public partial class TextBoxTabOutUserControl : UserControl
{
public static readonly DependencyProperty CodeValueProperty =
DependencyProperty.Register("CodeValue", typeof(string), typeof(TextBoxTabOutUserControl));
public string CodeValue
{
get { return (string)GetValue(CodeValueProperty); }
set { SetValue(CodeValueProperty, value); }
}
public TextBoxTabOutUserControl()
{
InitializeComponent();
}
}
Just bind both to LocTextBoxText
<Window x:Class="ProblemDemoWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:ProblemDemoWPF"
Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<loc:TextBoxTabOutUserControl Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="left"
CodeValue="{Binding Buscode, Mode=OneWayToSource}"/>
<Label Content="Busname" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left"></Label>
<TextBox Width="100" Grid.Row="1" Grid.Column="1" Text="{Binding Path=Busname, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Label Content="Busdestination" Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left"></Label>
<TextBox Width="100" Grid.Row="2" Grid.Column="1" Text="{Binding Path=Busdestination, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Label Content="Busmodel" Grid.Row="3" Grid.Column="0" HorizontalAlignment="Left"></Label>
<TextBox Width="100" Grid.Row="3" Grid.Column="1" Text="{Binding Path=Busmodel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</Grid>
</Window>
add Notify to property setter
class MainWindowViewModel : INotifyPropertyChanged
{
private String _buscode;
private string _busname;
private string _busdestination;
private string _busmodel;
public String Buscode
{
get
{
return _buscode;
}
set
{
if (_buscode != value)
{
_buscode = value;
Notify("Buscode");
FetchData(_buscode);
}
}
}
private void FetchData(string buscode)
{
//DB stuff
this.Busname = "Name 1234";
this.Busmodel = "Model 1234";
this.Busdestination = "Destination 1234";
}
public string Busname
{
get { return _busname; }
set { _busname = value; Notify("Busname"); }
}
public string Busdestination
{
get { return _busdestination; }
set { _busdestination = value; Notify("Busdestination"); }
}
public string Busmodel
{
get { return _busmodel; }
set { _busmodel = value; Notify("Busmodel"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Essentially, I have a markup issue. I have come up with a few solutions but I can't help but feel like this should be simpler. Rather than lead you down my convoluted path I thought I would share the simplest implementation and ask how you would address it.
MainPage.xaml
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="6" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="6" />
<ColumnDefinition />
<!--Additional Columns-->
</Grid.ColumnDefinitions>
<!--Row Definitions-->
<Label Grid.Row="0" Grid.Column="0" Content="Vin:" HorizontalAlignment="Right" />
<ctrl:CommandTextBox Grid.Row="0" Grid.Column="2" Command="{Binding CreateVehicleCommand}" CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}" />
<Label Grid.Row="0" Grid.Column="3" Content="Manufacturer:" HorizontalAlignment="Right" />
<TextBox Grid.Row="0" Grid.Column="5" IsEnabled="False" Text="{Binding Vehicle.Manufacturer, Mode=OneWay}" />
<!--Additional Read Only Values-->
</Grid>
Given the example above, how can I get the Contents of the Grid into a View given the constraint that the Command to create the vehicle is outside of the DataContext to be created(Vehicle)?
If you do wish to look at my specific attempt, that question is here UserControl's DependencyProperty is null when UserControl has a DataContext
how can I get the Contents of the Grid into a View given the
constraint that the Command to create the vehicle is outside of the
DataContext to be created(Vehicle)?
This feels like a race condition more than an MVVM problem. I will address the issue first but make a secondary suggestion after.
There are no reasons in which a ViewModel cannot contain another viewmodel as a reference and that reference is bound to using the INotifyPropertyChanged mechanisim.
Or that your xaml (view) page contains a static reference to a ViewModel which the page (view) does not directly use in its DataContext, but that a certain control cannot bind to that static outside of the data context of the containing control.
Either way one can provide access (as also mentioned in the response to the other post you provided) by pointing to itself to get data or to provide an alternate plumbing which gets the data.
Or you can flatten your viewmodel to contain more information and handle this IMHO race condition so that this situation doesn't arise and the control as well as the grid can access information in a proper format.
I can't fully address the problem because you are more aware of the design goals and hazards which now must be worked around.
I've come up with something, I'm relatively happy with. This has saved me from creating 100s of composite ViewModel's and while it does introduce some unnecessary complexity it does dramatically reduce the amount copy/paste code I need to write.
VMFactoryViewModel.cs
public class CreatedViewModelEventArgs<T> : EventArgs where T : ViewModelBase
{
public T ViewModel { get; private set; }
public CreatedViewModelEventArgs(T viewModel)
{
ViewModel = viewModel;
}
}
public class VMFactoryViewModel<T> : ViewModelBase where T : ViewModelBase
{
private Func<string, T> _createViewModel;
private RelayCommand<string> _createViewModelCommand;
private readonly IDialogService _dialogService;
/// <summary>
/// Returns a command that creates the view model.
/// </summary>
public ICommand CreateViewModelCommand
{
get
{
if (_createViewModelCommand == null)
_createViewModelCommand = new RelayCommand<string>(x => CreateViewModel(x));
return _createViewModelCommand;
}
}
public event EventHandler<CreatedViewModelEventArgs<T>> CreatedViewModel;
private void OnCreatedViewModel(T viewModel)
{
var handler = CreatedViewModel;
if (handler != null)
handler(this, new CreatedViewModelEventArgs<T>(viewModel));
}
public VMFactoryViewModel(IDialogService dialogService, Func<string, T> createViewModel)
{
_dialogService = dialogService;
_createViewModel = createViewModel;
}
private void CreateViewModel(string viewModelId)
{
try
{
OnCreatedViewModel(_createViewModel(viewModelId));
}
catch (Exception ex)
{
_dialogService.Show(ex.Message);
}
}
}
VMFactoryUserControl.cs
public class VMFactoryUserControl<T> : UserControl where T : ViewModelBase
{
public static readonly DependencyProperty VMFactoryProperty = DependencyProperty.Register("VMFactory", typeof(VMFactoryViewModel<T>), typeof(VMFactoryUserControl<T>));
public VMFactoryViewModel<T> VMFactory
{
get { return (VMFactoryViewModel<T>)GetValue(VMFactoryProperty); }
set { SetValue(VMFactoryProperty, value); }
}
}
GenericView.xaml
<ctrl:VMFactoryUserControl x:Class="GenericProject.View.GenericView"
x:TypeArguments="vm:GenericViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctrl="clr-namespace:SomeProject.Controls;assembly=SomeProject.Controls"
xmlns:vm="clr-namespace:GenericProject.ViewModel">
<Grid>
<!-- Column Definitions -->
<!-- Row Definitions -->
<Label Grid.Row="0" Grid.Column="0" Content="Generic Id:" HorizontalAlignment="Right" />
<ctrl:CommandTextBox Grid.Row="0" Grid.Column="2"
Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}" />
<Label Grid.Row="0" Grid.Column="3" Content="Generic Property:" HorizontalAlignment="Right" />
<TextBox Grid.Row="0" Grid.Column="5" IsEnabled="False" Text="{Binding GenericProperty, Mode=OneWay}" />
<!--Additional Read Only Values-->
</Grid>
</ctrl:VMFactoryUserControl>
GenericView.xaml.cs
public partial class GenericView : VMFactoryUserControl<GenericViewModel>
{
public GenericView()
{
InitializeComponent();
}
}
MainPageViewModel.cs
public class MainPageViewModel : ViewModelBase
{
private readonly IDialogService _dialogService;
private GenericViewModel _generic;
private readonly VMFactoryViewModel<GenericViewModel> _genericFactory;
public GenericViewModel Generic
{
get { return _generic; }
private set
{
if (_generic != value)
{
_generic = value;
base.OnPropertyChanged("Generic");
}
}
}
public VMFactoryViewModel<GenericViewModel> GenericFactory
{
get { return _genericFactory; }
}
private void OnGenericFactoryCreatedViewModel(object sender, CreatedViewModelEventArgs<GenericViewModel> e)
{
Generic = e.ViewModel;
}
public MainPageViewModel(IDialogService dialogService)
{
_dialogService = dialogService;
_genericFactory = new VMFactoryViewModel<GenericViewModel>(_dialogService, x => new GenericViewModel(_dialogService, GetGeneric(x)));
_genericFactory.CreatedViewModel += OnGenericFactoryCreatedViewModel;
}
private Generic GetGeneric(string genericId)
{
// Return some Generic model.
}
}
MainPage.xaml
<Page x:Class="GenericProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vw="clr-namespace:GenericProject.View">
<StackPanel>
<!-- Headers and Additional Content. -->
<vw:EventView DataContext="{Binding Generic}"
VMFactory="{Binding DataContext.GenericFactory, RelativeSource={RelativeSource AncestorType={x:Type Page}}}" />
</StackPanel>
</Page>
I already have a working ListBox with Items from my local database. Now I wanted to upgrade this to a CollectionViewSource for filtering. After my upgrade the new ListBox with CollectionViewSource shows nothing.
MainPage Code Behind:
// Data context for the local database
private BuildingDataContext toDoDB;
// Define an observable collection property that controls can bind to.
private ObservableCollection<Building> _buildings;
public ObservableCollection<Building> BuildingTable
{
get
{
return _buildings;
}
set
{
if (_buildings != value)
{
_buildings = value;
NotifyPropertyChanged("BuildingTable");
}
}
}
public CollectionViewSource Source { get; set; }
// Konstruktor
public MainPage()
{
InitializeComponent();
// Connect to the database and instantiate data context.
toDoDB = new BuildingDataContext(BuildingDataContext.DBConnectionString);
// Data context and observable collection are children of the main page.
this.DataContext = this;
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
// Define the query to gather all of the to-do items.
var toDoItemsInDB = from Building todo in toDoDB.BuildingTable
select todo;
// Execute the query and place the results into a collection.
BuildingTable = new ObservableCollection<Building>(toDoItemsInDB);
Source = new CollectionViewSource();
Source.Source = BuildingTable;
// Call the base method.base.OnNavigatedTo(e);
}
For that purpose I added the lines:
public CollectionViewSource Source { get; set; }
Source = new CollectionViewSource();
Source.Source = BuildingTable;
I tried as well to put
Source = new CollectionViewSource();
Source.Source = BuildingTable;
in my MainPage Constructor. It doesnt work as well.
My Mainpage.xaml:
<!--<ListBox x:Name="toDoItemsListBox" ItemsSource="{Binding BuildingTable}" Grid.Row="0" Margin="12, 0, 12, 0" Width="440" SelectionChanged="goToNavigation">-->
<ListBox x:Name="toDoItemsListBox" ItemsSource="{Binding Source.View}" Grid.Row="0" Margin="12, 0, 12, 0" Width="440" SelectionChanged="goToNavigation">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Width="440">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Name="textBlockShortcut" Text="{Binding Shortcut}" Width="Auto" HorizontalAlignment="Left" Grid.Column="0" Margin="0,0,0,5" FontSize="36" />
<TextBlock Name="textBlockName" Text="{Binding BuildingName}" Width="Auto" HorizontalAlignment="Left" Grid.Column="1" Margin="0,0,0,5" FontSize="36" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The first commented line shows the old working listbox without CollectionViewSource. So what am I missing?
EDIT:
private void goToNavigation(object sender, RoutedEventArgs e)
{
// If selected index is -1 (no selection) do nothing
if (toDoItemsListBox.SelectedIndex == -1)
return;
// Navigate to the new page
PhoneApplicationService.Current.State["SelectedItem"] = toDoItemsListBox.SelectedItem;
NavigationService.Navigate(new Uri("/NavigationPage.xaml", UriKind.Relative));
// Reset selected index to -1 (no selection)
toDoItemsListBox.SelectedIndex = -1;
}
You would usually create and bind to a CollectionViewSource in XAML:
<UserControl.Resources>
<CollectionViewSource x:Key="cvs"/>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource cvs}}" ...>
...
</ListBox>
</Grid>
and in code-behind just access that CollectionViewSource like this:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
...
var cvs = Resources["cvs"] as CollectionViewSource;
cvs.Source = BuildingTable;
}
You don't use the CollectionViewSource class directly, you use a CollectionView of the appropriate type.
View = CollectionViewSource.GetDefaultView( myCollection );
and then you bind that directly to your source.
ItemsSource="{Binding View}"
You can and only should use a CollectionViewSource from xaml, because thats its main purpose. From code you should directly create a CollectionView or use the GetDefaultView method.