Updating data binding source of a content control in a ResourceDictionary - c#

I want to create a view that has a set of tabs (each, basically a ContentControl) that each have various settings. I then want to have a button that will update all of the data binding rather than updating instantly or having update buttons associated with the controls themselves
So, my control is MEF exported as a ResourceDictionary and is similar to the below
<ResourceDictionary ...>
<DataTemplate DataType="{x:Type vm:AdminViewModel}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TabControl Grid.Row="0">
<TabItem Header="Tests">
<ContentControl Content="{Binding ResultStorage}"/>
</TabItem>
</TabControl>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="Update"/>
<Button Content="Cancel"/>
</StackPanel>
</Grid>
</DataTemplate>
</ResourceDictionary>
TestStorage would be like this:
<ResourceDictionary ...>
<DataTemplate DataType="{x:Type data:XmlResultStorage}">
<StackPanel>
<TextBlock Text="Result File Path:"/>
<TextBox Text="{Binding Path=ResultPath, Source={x:Static properties:DataStorage.Default}, UpdateSourceTrigger=Explicit}"/>
<TextBlock Text="Result File Location:"/>
<TextBox Text="{Binding Path=ResultFilename, Source={x:Static properties:DataStorage.Default}, UpdateSourceTrigger=Explicit}"/>
</StackPanel>
</DataTemplate>
What I want to do is when the button to update is pressed to somehow call the update (UpdateSource?) on the ContentControl but I can't see how to do it.
In an ideal world I wouldn't have code-behind and do it all via MVVM or something, but if that's not possible code-behind is fine.
So I have two issues, how do I update data bindings manually via a ResourceDictionary and how do I then cause it to cascade through its child ContentControls?

Related

Multiple usercontrol not reflected while setting DataTemplate to ViewModel in MVVM

I am trying to add two usercontrols in one view
usercontrol working fine which get call in constructor i am implementing INotifyPropertyChanged for 'SelectedViewModel'.
property get updated for second usercontrol but not reflected in view .
thanks in advance
<Window x:Class="MovieBookinGWithUserControl.View.StartupWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MovieBookinGWithUserControl.ViewModel"
xmlns:localview ="clr-namespace:MovieBookinGWithUserControl.View"
Title="StartupWindow" Height="AUTo" Width="Auto">
<Window.Resources>
<DataTemplate DataType="{x:Type local:LoginWIndowViewModel}">
<localview:LoginWindow></localview:LoginWindow>
</DataTemplate>
<DataTemplate DataType="{x:Type local:MovieInfoViewModel }">
<localview:MovieInfo ></localview:MovieInfo>
</DataTemplate>
</Window.Resources>
<Grid>
<DockPanel LastChildFill="True">
<StackPanel x:Name="navigation" DockPanel.Dock="Left" Width="Auto">
</StackPanel>
<ContentControl x:Name="Pages" DockPanel.Dock="Right" Content="{Binding SelectedViewModel,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DockPanel>
</Grid>
</Window>

ViewModels with MVVLight

I am new in MVVM Light. I try to write a project with MVVM Light Framework and I have some questions. I have defined my MainWindow.xaml like this:
MainWindow.xaml:
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Height="30" Width="40" Command="{Binding OpenNews}">News</Button>
<Button Height="30" Width="40" Margin="5,0,0,0" Command="{Binding OpenTeams}">Teams</Button>
</StackPanel>
<ContentPresenter Grid.Row="1" Content="{Binding CurrentViewModel}"/>
</Grid>
MainViewModel:
Contains the two RelayCommands OpenNews and OpenTeams. And also the CurrentViewModel property. This property contains the ViewModel depending on the option which the user clicks (NewsViewModel or TeamsViewModel).I do that like this:
private void ShowOpenTeams()
{
CurrentViewModel = ServiceLocator.Current.GetInstance<TeamsViewModel>(); //MainViewModel.TeamsViewModel;
}
private void ShowOpenNews()
{
CurrentViewModel = ServiceLocator.Current.GetInstance<NewsViewModel>();
}
app.xaml:
I have defined the relation between ViewModel's DataType and the View that should be shown.
<Application.Resources>
<DataTemplate DataType="{x:Type vm:NewsViewModel}">
<view:NewsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:TeamsViewModel}">
<view:TeamView/>
</DataTemplate>
<!--Global View Model Locator-->
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True" />
</Application.Resources>
The question is: why do I need the ViewModelLocator in this case? Does using ViewModelLocator make sense with this approach? I don't see any benefit...
The Views in MVVM Light are created with this relation DataContext="{Binding ViewModelName, Source={StaticResource Locator}}" but I wonder which approach is it usefull with?

Custom ListView from inside HubAppSection -- Windows Phone 8.1

I am currently building a Windows Phone Applicaation, based off of the HubAppTemplate.
The template comes with a sample .JSON data source that it uses to populate the data of each HubSection. However, I want to use a non JSON type of data as the basis of my code. Inside my C# code, I need to make a function call to my backend to get the type of data I want out of it.
I can put this data inside of my own custom list (on the C# side), but how can I make that list act as the data source for my HubSection? Any old listview/list box works perfectly. Basically, I need help wiring the C# to the XAML -- the main issue is that I cannot access my listView inside of the datatemplate by name.
Can anyone give me some pointers to get going in the right direction?
Here is some reference code to show you what I am talking about:
<HubSection x:Uid="Clubs" Header="Clubs" DataContext="{Binding Groups}" HeaderTemplate="{ThemeResource HubSectionHeaderTemplate}">
<DataTemplate>
<ListView Name="ClubsList"
IsItemClickEnabled="True"
ItemsSource="{Binding}"
ItemClick="GroupSection_ItemClick"
ContinuumNavigationTransitionInfo.ExitElementContainer="True">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,27.5">
<TextBlock Text="{Binding Title}" Style="{ThemeResource ListViewItemTextBlockStyle}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</HubSection>
The above XAML is basically pulled straight from the hubapp template. I want to be able to use my own itemssource inside of that ListView that is generated from my C# code -- however, I cannot figure out how this ItemsSource works. I also cannot access my listview by name (ClubsList).
Here is the initialization code going on up top (wasn't sure if it was important to post this or not):
<Page
x:Class="HubAppTemplate.HubPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:HubAppTemplate"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="using:HubAppTemplate.Data"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
d:DataContext="{Binding Source={d:DesignData Source=/DataModel/SampleData.json, Type=data:SampleDataSource}}"
mc:Ignorable="d">
<Page.Resources>
<DataTemplate x:Key="HubSectionHeaderTemplate">
<TextBlock Margin="0,0,0,-9.5" Text="{Binding}"/>
</DataTemplate>
<!-- Grid-appropriate item template as seen in section 2 -->
<DataTemplate x:Key="Standard200x180TileItemTemplate">
<Grid Margin="0,0,9.5,9.5" Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}">
<Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}" Height="138.5" Width="138.5"/>
<TextBlock Text="{Binding Title}" VerticalAlignment="Bottom" Margin="9.5,0,0,6.5" Style="{ThemeResource BaseTextBlockStyle}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="StandardTripleLineItemTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}" Margin="0,9.5,0,0" Grid.Column="0" HorizontalAlignment="Left">
<Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}" Height="79" Width="79"/>
</Border>
<StackPanel Grid.Column="1" Margin="14.5,0,0,0">
<TextBlock Text="{Binding Title}" Style="{ThemeResource ListViewItemTextBlockStyle}"/>
<TextBlock Text="{Binding Description}" Style="{ThemeResource ListViewItemContentTextBlockStyle}" Foreground="{ThemeResource PhoneMidBrush}" />
<TextBlock Text="{Binding Subtitle}" Style="{ThemeResource ListViewItemSubheaderTextBlockStyle}" />
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate x:Key="StandardDoubleLineItemTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border Background="{ThemeResource ListViewItemPlaceholderBackgroundThemeBrush}" Margin="0,9.5,0,0" Grid.Column="0" HorizontalAlignment="Left">
<Image Source="{Binding ImagePath}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}" Height="79" Width="79"/>
</Border>
<StackPanel Grid.Column="1" Margin="14.5,0,0,0">
<TextBlock Text="{Binding Title}" Style="{ThemeResource ListViewItemTextBlockStyle}"/>
<TextBlock Text="{Binding Subtitle}" Style="{ThemeResource ListViewItemSubheaderTextBlockStyle}"/>
</StackPanel>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid x:Name="LayoutRoot">
<Hub x:Name="Hub" x:Uid="Hub" Header="Club Alert" Background="{ThemeResource HubBackgroundImageBrush}">
It is pulling from the JSON backend, but I want to just use my own custom listview for each section. Deleting the DataSource and data template headers gives me errors, however.
Thank you so much for your help in advance!
--A total newbie
HubSection elements require their contents to be populated via a template, so you can't just remove the <DataTemplate> tags, unfortunately. However, there is a simple way to accomplish what you are trying to do, if I understand you correctly.
If you're starting with the default Hub template, you should have this function in your HubPage.xaml.cs file
private async void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
// TODO: Create an appropriate data model for your problem domain to replace the sample data
var sampleDataGroups = await SampleDataSource.GetGroupsAsync();
this.DefaultViewModel["Groups"] = sampleDataGroups;
MainViewModel viewModel = DataContext as MainViewModel;
if (!viewModel.IsDataLoaded)
{
viewModel.Load();
}
}
this.DefaultViewModel is just a Dictionary, and they have loaded the sample JSON into a variable and stored this in the ["Groups"] key of the dictionary. Since the Page's DataContext is being bound to {Binding DefaultViewModel, RelativeSource={RelativeSource Self}}, the HubSection's DataContext is being bound to {Binding Groups}, and the ItemsSource of the ListView in each DataTemplate is being bound to {Binding}, each element of the loaded JSON is being used to fill the items of the ListView.
A simple solution would be to assign this.DefaultViewModel["Groups"] to the C# List you are creating from the data you load from your back end.
Something like this:
private async void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
{
// TODO: Create an appropriate data model for your problem domain to replace the sample data
var myData = await GetListOfThingsFromBackend();
this.DefaultViewModel["Groups"] = myData;
MainViewModel viewModel = DataContext as MainViewModel;
if (!viewModel.IsDataLoaded)
{
viewModel.Load();
}
}
A better approach would probably be to separate out all ViewModel functionality to it's own class that is better suited to your needs, and then adjust the various DataContext properties throughout the XAML, but that would likely take more time. I can elaborate if needed, but the simple solution is probably enough for now.

WPF Expander overlay used in ItemsControl

I've been searching for some time but all solution I find only tackle parts of my issue.
I'd like to have an ItemsControl with each item containing an Expander. On expanding, the Expander's content should be shown as an overlay overlapping the other items in the ItemsControl and not moving them down.
The following XAML-Code does exactly as supposed to with one big issue: The Expander's content does not overlap the other items but is hidden behind them. I suppose this is due to the ZIndex as the following items in the ItemsControl are added after the Expander's content.
I managed to set one single Expander's ZIndex to 99 using style triggers but this seems to be a too complicated and error-prone solution. Any thoughts?
<Window x:Class="WpfTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Window.Resources>
<x:Array x:Key="items"
Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String>One</sys:String>
<sys:String>Two</sys:String>
<sys:String>Three</sys:String>
<sys:String>Four</sys:String>
</x:Array>
<DataTemplate x:Key="template">
<Grid Background="Red" Margin="0,0,0,10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding}" />
<Canvas Height="25" Grid.Row="1">
<Expander ExpandDirection="Down" Header="Header" Grid.Row="1">
<Expander.Content>
<TextBlock Height="80" Text="Content" Background="Yellow" />
</Expander.Content>
</Expander>
</Canvas>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ScrollViewer>
<ItemsControl ItemsSource="{StaticResource items}"
ItemTemplate="{StaticResource template}"
HorizontalContentAlignment="Stretch">
</ItemsControl>
</ScrollViewer>
</Grid>
</Window>
It's going to be tricky to achieve this in the way you are attempting it. The problem is the nested structure -- since each Canvas is nested inside a Grid, you won't be able to control its z-index relative to other Canvas element. To illustrate this, here is a schematic of the visual tree created by your current markup:
<StackPanel> <!-- items panel -->
<ContentPresenter> <!-- item wrapper -->
<Grid>
<Canvas>
</Canvas>
</Grid>
</ContentPresenter>
<ContentPresenter> <!-- item wrapper -->
<Grid>
<Canvas>
</Canvas>
</Grid>
</ContentPresenter>
</StackPanel>
With reference to the above, your goal will be to have the Canvas elements appear in front of the siblings of its parent ContentPresenter. This is impossible in this hierarchy, because ZIndex only applies relative to siblings of the same parent element. Now, there might be ways you could massage the above into a flat structure so that you could then apply ZIndex your expanded content as needed.
However, I think an easier and more natural approach would be to use Popup elements for the expanded content. A Popup is a framework primitive that is located outside of the visual tree, and will always sit on top of your other content. You can use a ToggleButton or something similar to create the "expand" effect. For example:
<StackPanel Grid.Row="1">
<ToggleButton x:Name="PopupToggle" Content="Expand" />
<Popup IsOpen="{Binding IsChecked,ElementName=PopupToggle}">
<TextBlock Height="80" Text="Content" Background="Yellow" />
</Popup>
</StackPanel>

Change the TextBlock Text present in a DataTemplate

I have a DataTemplate in which I have a Grid layout with 2 RowDefinitions. I have a TextBox in the first row and a ComboBox in the second row.
I have defined the DataTemplate in my ResourceDictionary.
This is the code for the DataTemplate:
<DataTemplate x:Key="myDataTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Name ="txtChannelDescription" Grid.Row="0" Margin="1,1,1,1"/>
<ComboBox Name="cmbChannelTag" Grid.Row="1" IsReadOnly="True" Margin="1,1,1,1"/>
</Grid>
</DataTemplate>
I am using this DataTemplate in code behind as:
(DataTemplate)FindResource("myDataTemplate")
How do I set the Value of TextBox.Text and the ItemSource of the ComboBox at runtime?
I am using the DataTemplate as template for DataGridTemplateColumn.Header.
Create a custom data type that suits your purpose (with properties named ChannelDescription and ChannelTag in this example) and then bind the values to your DataTemplate:
<DataTemplate x:Key="myDataTemplate" DataType="{x:Type NamespacePrefix:YourDataType}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Text="{Binding ChannelDescription}" Grid.Row="0" Margin="1,1,1,1"/>
<ComboBox ItemsSource="{Binding ChannelTag}" Grid.Row="1" IsReadOnly="True"
Margin="1,1,1,1"/>
</Grid>
</DataTemplate>
It would be used like this:
In your view model, you would have a collection property, lets say called Items:
public ObservableCollection<YourDataType> Items { get; set; }
(Your property should implement the INotifyPropertyChanged interface unlike this one)
In your view, you would have a collection control, lets say a ListBox:
<ListBox ItemsSource="{Binding Items}" ItemTemplate="{StaticResource myDataTemplate}" />
Then each item in the collection control will have the same DataTemplate, but the values would come from the instances of type YourDataType in the Items collection.

Categories

Resources