How to fill ContentPresenter by binding - c#

I've a problem to connect.
I started to connect my tabs with a tabcontrol.ressources and it worked to show the text of each tabs.
Then I wanted to had a scroll for my TabItems and it doesn't work, nothing shows in tab... I can't even use tabcontrol.ressources anymore...
<DockPanel>
<Button Background="DarkGoldenrod" Height="Auto" Command="{Binding OpenFlyoutDataCommand}">
<StackPanel>
<materialDesign:PackIcon Kind="ArrowRightBoldCircleOutline" Width="30" Height="30"/>
</StackPanel>
</Button>
<TabControl ItemsSource="{Binding TabEDCWaferData, Mode=TwoWay}"
SelectedItem="{Binding SelectedTabEDCWaferData}">
<!-- Used to create a scroolbar for tabitems -->
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden" >
<TabPanel Grid.Column="0" Grid.Row="0"
Margin="2,2,2,0" IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter ContentSource="..."/>
</Grid>
</ControlTemplate>
</TabControl.Template>
<!-- Contains the text in the tab item ! -->
<TabControl.Resources>
<DataTemplate DataType="TabItem">
<DockPanel>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}}, Path=Content}" />
</DockPanel>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</DockPanel>
This is connected to a collection of TabItem, where I've a function to add Items binding to an other button.
private ObservableCollection<TabItem> _TabEDCWaferData;
public ObservableCollection<TabItem> TabEDCWaferData
{
get { return _TabEDCWaferData; }
set
{
_TabEDCWaferData = value;
RaisePropertyChanged("TabEDCWaferData");
}
}
public void AddTabItem(string name)
{
TabItem tab = new TabItem();
tab.Header = name;
tab.Content = "Temporary content";
TabEDCWaferData.Add(tab);
}
I read that I have to use the ContentPresenter, but I don't know how to bind it. I think this is not working with TabItems...
I just want to bind it as I did in the Ressources by using the ContentPresenter.
I hope that I'm clear enough ! Thanks
EDIT : I try to display in the ContentPresenter the selected item tab content that I add in the function `AddTabItem.

With ContentPresenter, most times, this does the job:
<ContentPresenter />
The default ContentSource is "Content". That means it'll look at the Content property of the templated parent and it'll take whatever it finds there for its own content.
But that doesn't help you at all, and you don't have to use ContentPresenter; it's just a convenience. In this case, the content you want to present is SelectedItem.Content, which isn't a valid ContentSource for ContentPresenter. But you can do the same thing with a binding on a ContentControl instead:
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ScrollViewer
Grid.Row="0"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Hidden"
>
<TabPanel
Grid.Column="0"
Margin="2,2,2,0" IsItemsHost="true"/>
</ScrollViewer>
<ContentControl
Grid.Row="1"
Content="{Binding SelectedItem.Content, RelativeSource={RelativeSource TemplatedParent}}"
/>
</Grid>
</ControlTemplate>
</TabControl.Template>
TemplateBinding isn't going to work with a Path such as "SelectedItem.Content"; it only accepts names of properties on the templated parent. I fixed your Grid.Row attributes, too.
Also, you may as well delete that DataTemplate for TabItem that you put in TabControl.Resources. That's not what DataTemplate is for; you use DataTemplates to define visual presentations for your viewmodel classes, but TabItem is a control. It already knows how to display itself, and in fact that DataTemplate is being ignored, so it's best not to leave it there; you'll only waste time later on making changes to it and trying to figure out why it's not having any effect. Your TabItems will display correctly without it.

Try something like this ?
<ContentPresenter Content="{TemplateBinding Content}" />
Edit
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" />

Related

How to get webview2 control in list control

I want to pass the webview2 control on the page to the ViewModel after clicking the button.
The following is part of the code for xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel>
<!--Click the button "Button_single", and the value of parameter can be obtained from ViewModel as Microsoft.Web.WebView2.Wpf.WebView2 type-->
<Button Command="{Binding BtnCommand1}" CommandParameter="{Binding ElementName=webView_single}">Button_single</Button>
<!--Click the button "Button_list", and the value of parameter obtained from ViewModel is null. Why?-->
<Button Command="{Binding BtnCommand1}" CommandParameter="{Binding ElementName=webView_list}">Button_list</Button>
<webview2:WebView2 Name="webView_single" Source="https://www.google.com/" Grid.Row="1">
<behaviour:Interaction.Triggers>
<behaviour:EventTrigger EventName="NavigationCompleted">
<behaviour:CallMethodAction TargetObject="{Binding}" MethodName="webView2_NavigationCompleted" />
</behaviour:EventTrigger>
</behaviour:Interaction.Triggers>
</webview2:WebView2>
</StackPanel>
<ScrollViewer Grid.Row="2" >
<ItemsControl Grid.Row="1" ItemsSource="{Binding AccountDtos}" HorizontalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<md:TransitioningContent OpeningEffect="{md:TransitionEffect Kind=ExpandIn}" >
<Grid Width="600" MinHeight="800" MaxHeight="250" Margin="8" >
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border CornerRadius="4" Grid.RowSpan="5" Background="#7F7F7F" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserName:" />
<TextBlock Padding="5,0" Text="{Binding UserName}" />
<TextBlock Text="Password:" />
<TextBlock Padding="5,0" Text="{Binding Password}" />
</StackPanel>
<webview2:WebView2 Name="webView_list" Source="https://www.google.com/" Grid.Row="1">
<behaviour:Interaction.Triggers>
<behaviour:EventTrigger EventName="NavigationCompleted">
<behaviour:CallMethodAction TargetObject="{Binding}" MethodName="webView2_NavigationCompleted" />
</behaviour:EventTrigger>
</behaviour:Interaction.Triggers>
</webview2:WebView2>
</Grid>
</md:TransitioningContent>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
I want to pass the webview2 control on the page to the ViewModel after clicking the button.
The following is part of the code for xaml:
public DelegateCommand<object> BtnCommand1 { get => new DelegateCommand<object>(Execute1); }
private void Execute1(object parameter)
{
//In the xaml page, if you click the button "Button_single", you can get the parameter value of Microsoft.Web.WebView2.Wpf.WebView2 type
//If you click the button "Button_list", the value of parameter is null. Why and how can it not be null?
Console.Write(parameter);
}
I want to get the webView control in the list control in the ViewModel. How should I write this code?
(This question Passing a Different button in Command Parameter Wpf Similar to my question, but different)
There are two reasons the webView_list commandparameter cannot work.
The first is conceptual. There are going to be a list of those WebView2s rather than just one. How's it supposed to know which one it is to reference?
You know if you have a list of anything in c# then you'd have to reference by index or something which one in the list you want.
The same would be true for xaml. You'd need to tell it to go look at row 0 or 1 or 2 or whatever.
There is another complication though. WPF has the concept of namescopes. All the controls directly in the grid of your window are in the same namescope.
When you have templates and in particular lists of things that becomes a bit more complicated.
Let's think about this from the bottom up though.
You've named a control webView_list. This is in an itemtemplate. So when you have 4 items you will have 4 WebView2s. But it will not error. Somehow those 4 named controls do not collide with one another.
This is because each of those items produced from that itemtemplate have their own namescope. The first item has a namescope of it's own, the second has it's own namescope etc.
You cannot just use elementname to reference something in a different namescope. So you can't get at a specific webview2 in that list using elementname from outside the item it's in.
In any case, you need some way to tell which item.
You could use selecteditem if this was a listbox.
An itemscontrol has no selection though. To get at your webview2 in an item the usual thing to do would be to put the button in the itemtemplate and the same namescope. That could then use relativesource to get to the command in the parent datacontext. Something like:
<ItemsControl.ItemTemplate>
<DataTemplate>
<md:TransitioningContent OpeningEffect="{md:TransitionEffect Kind=ExpandIn}" >
<Grid Width="600" MinHeight="800" MaxHeight="250" Margin="8" >
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Border CornerRadius="4" Grid.RowSpan="5" Background="#7F7F7F" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="UserName:" />
<TextBlock Padding="5,0" Text="{Binding UserName}" />
<TextBlock Text="Password:" />
<TextBlock Padding="5,0" Text="{Binding Password}" />
</StackPanel>
<Button Command="{Binding DataContext.BtnCommand1, RelativeSource={RelativeSource AncestorType ItemsControl}}"
CommandParameter="{Binding ElementName=webView_list}" Grid.Row="2">Button_list</Button>
<webview2:WebView2 Name="webView_list" Source="https://www.google.com/" Grid.Row="2">
<behaviour:Interaction.Triggers>
<behaviour:EventTrigger EventName="NavigationCompleted">
<behaviour:CallMethodAction TargetObject="{Binding}" MethodName="webView2_NavigationCompleted" />
</behaviour:EventTrigger>
</behaviour:Interaction.Triggers>
</webview2:WebView2>
</Grid>
</md:TransitioningContent>
</DataTemplate>
</ItemsControl.ItemTemplate>

UserControl in DataTemplate does not bind correctly

I've been trying to create a custom menu. For this reason I wanted to use an ItemsControl in order to make it flexible. After hours of headache I figured out how to make it - kinda.
I have my custom ItemsControl "LiftMenu" (which is not yet much custom but standard) and an UserControl called "LiftItem". Last but not least I got the Model-class "LiftMenuItem".
By adding a new LiftMenuItem to the LiftMenu, it should display a new LiftItem-control as corresponding item. So far so good, I managed to get this working.
In that LiftItem-control I bind like I would in a normal DataTemplate: plain bindings with a path, nothing more. Normally this would work just fine because the DataTemplate already has it's context set to the model-type.
But now I just get an empty control that does nothing and shows nothing, because the bindings don't work.
I implemented it this way:
<menu:LiftMenu HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="200" Background="#80A8A8A8" Margin="5,0,0,0">
<menu:LiftMenu.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</menu:LiftMenu.ItemsPanel>
<menu:LiftMenu.ItemTemplate>
<DataTemplate DataType="{x:Type menu:LiftMenuItem}">
<menu:LiftItem />
</DataTemplate>
</menu:LiftMenu.ItemTemplate>
<menu:LiftMenuItem Header="Test1"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border x:Name="border" BorderBrush="{Binding LabelColor}" BorderThickness="1" HorizontalAlignment="Left" Height="Auto" Margin="0"
VerticalAlignment="Stretch" Width="4" Background="{Binding BorderBrush, ElementName=border}"/>
<TextBlock Text="{Binding Header}" TextTrimming="CharacterEllipsis" Margin="5,0,5,0" HorizontalAlignment="Left" VerticalAlignment="Center"
Foreground="White" />
<controls:ProgressRing x:Name="ring" Grid.Column="2" HorizontalAlignment="Right" Stroke="#ffff8000" VerticalAlignment="Center"
Minimum="0" Maximum="100" Value="{Binding ProcessValue}" IsIndeterminate="{Binding ProcessIndeterminate}" Visibility="{Binding ProcessVisibility}"
Width="20" Height="20" Radius="10" Margin="2,0,2,0" />
</Grid>
In the end there is no text, no border. Just the ProgressRing is visible.
How can I fix this? This ListItem-control should become similiar to a button, thus I need to do some styling (animation, ...). I can't do this within a normal DataTemplate, but I don't want to miss the binding features of WPF on that. This would make it relatively unflexible.
What's the problem? I probably just miss some DataContext or so, but I don't know what it would be.

Binding command to a templated user control

I'm new to WPF, but have been able make a lot of progress in short time thanks to a good book on the topic, and of course, quality posts on sites like this one. However, now I've come across something I can seem to figure out by those means, so I posting my first question.
I've have a ControlTemplate in a resource dictionary which I apply to several UserControl views. The template provides a simple overlay border and two buttons: Save and Cancel. The templated user control holds various text boxes, etc., and is bound to some ViewModel depending on the context. I'm trying to figure out how to bind the commands to the Save/Cancel buttons when I use/declare the UserControl in some view. Is this is even possible, or am I doing something very wrong?
First, the template:
<ControlTemplate x:Key="OverlayEditorDialog"
TargetType="ContentControl">
<Grid>
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="DarkGray"
Opacity=".7"/>
<Border HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="DarkGray">
<Grid>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0"/>
<Grid Grid.Row="1"
Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1"
Content="Cancel"
***Command="{Binding CancelCommand}}"**
/>
<Button Grid.Column="0"
Content="Save"
***Command="{Binding Path=SaveCommand}"***/>
</Grid>
</Grid>
</Border>
</Grid>
</ControlTemplate>
The template in turn is used in the CustomerEditorOverlay user control
<UserControl x:Class="GarazhApp.View.CustomerEditorOverlay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<UserControl.Resources>
<ResourceDictionary Source="Dictionary1.xaml"/>
</UserControl.Resources>
<ContentControl Template="{StaticResource ResourceKey=OverlayEditorDialog}">
<Grid Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<SomeElement/>
<SomeOtherElement/>
</Grid>
</ContentControl>
...and finally, the user control is used as part of a view like so:
<local:CustomerEditorOverlay Visibility="{Binding Path=CustomerViewModel.ViewMode, Converter={StaticResource myConverter}, FallbackValue=Collapsed}"
d:IsHidden="True" />
So, based on what I've learned from a project I have been on forever and a half, we have a workable pattern.
Let's say you have a bunch of modal windows that all get applied the same style within the application. To have Save and Cancel buttons on each view, the UserControl used for all of the modal windows has several dependency properties. In addition, we specify virtual methods for your commands (e.g. OnSaveCommand, OnCancelCommand, CanExecuteSaveCommand, CanExecuteCancelCommand) and the commands themselves as properties in a base ViewModel that is inherited by your views.
Ultimately, what happens is we create new modal windows by simply doing this:
<my:YourBaseView x:class="MyFirstView" xmlns:whatever="whatever" [...]>
<my:YourBaseView.PrimaryButton>
<Button Content="Save" Command="{Binding SaveCommand}" />
</my:YourBaseView.PrimaryButton>
<!-- some content -->
</my:YourBaseView>
With accompanying code-behind:
public class MyFirstView : YourBaseView
{
[Import] /* using MEF, but you can also do MvvmLight or whatever */
public MyFirstViewModel ViewModel { /* based on datacontext */ }
}
And a ViewModel:
public class MyFirstViewModel : ViewModelBase
{
public override OnSaveCommand(object commandParameter)
{
/* do something on save */
}
}
The template for this UserControl specifies ContentControls in a grid layout with the Content property bound to the PrimaryButton and SecondaryButton. Of course, the content for the modal is stored in the Content property of the UserControl and displayed in a ContentPresenter as well.
<Style TargetType="{x:Type my:YourBaseView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:YourBaseView}">
<Grid>
<!-- ignoring layout stuff -->
<ContentControl Content="{TemplateBinding Content}" />
<ContentControl Content="{TemplateBinding PrimaryButton}" />
<ContentControl Content="{TemplateBinding SecondaryButton}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
UserControl code:
public class YourBaseView : UserControl
{
public static readonly DependencyProperty PrimaryButtonProperty =
DependencyProperty.Register("PrimaryButton", typeof(Button), typeof(YourBaseView), new PropertyMetadata(null));
public Button PrimaryButton
{
get { return (Button)GetValue(PrimaryButtonProperty); }
set { SetValue(PrimaryButtonProperty, value); }
}
/* and so on */
}
You can change the style for each instance of your templated view, of course. We just happen to stick with one base style.
TL;DR edit: I may have gone a bit overboard since I think you just need the understanding that exposing dependency properties of type Button which are set up through the XAML each time you create a new overlay. That, or you could probably RelativeSource your way back up to the visual tree with something like {Binding DataContext.SaveCommand, RelativeSource={RelativeSource AncestorType={x:Type MyView}}} but it's a little dirtier.

Binding different Target on loaded DataTemplateSelector

I am using a DataTemplateSelector to select different UserControls (reference http://tech.pro/tutorial/807/wpf-tutorial-how-to-use-a-datatemplateselector), according the selected path I select the needed UserControl.
The problem is now, when using the WebBrowser Control, I should bind it to ActualHight of MyScrollViewer, but on all others it works with Hight or else the scroll bar is displayed bad. Must come from the WebBrowser control.
How can I switch the Bindings in the ContentControl between Hight/ActualHight depending of the loaded UserControl?
<DataTemplate x:Key="WebTemplate1">
<DockPanel LastChildFill="True">
<controls:WebBrowserUserControl SourceHtml="{Binding Converter={StaticResource UriConverter1}}" />
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="ImgTemplate1">
<Image Source="{Binding Converter={StaticResource RelativeToAbsolutePathConverter1}}"
Stretch="None" />
</DataTemplate>
...
<ScrollViewer Name="MyScrollViewer"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
DockPanel.Dock="Left"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<Grid x:Name="MyGridHelper">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<DockPanel x:Name="MyDockPanel" Dock="Top" HorizontalAlignment="Left">
<ContentControl x:Name="MyContentControl"
Width="{Binding ElementName=MyScrollViewer,
Path=Width/ActualWidth}"
Height="{Binding ElementName=MyScrollViewer,
Path=Height/ActualHight}"
Content="{Binding Path=CurrentItem1,
Mode=OneWay}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}" />
</DockPanel>
</Grid>
</ScrollViewer>
The DataTemplateSelector by itself cannot affect the other properties of the ContentControl, however, you could use a Converter to determine the Width\Height based on the same logic used to determine which template to use. So something like this:
<ContentControl x:Name="MyContentControl"
Width="{Binding ElementName=MyScrollViewer, Converter={StaticResource MyWidthConverter}, ConverterParameter="???"}"
Height="{Binding ElementName=MyScrollViewer, Converter={StaticResource MyHeightConverter}, ConverterParameter="???"}"
Content="{Binding Path=CurrentItem1, Mode=OneWay}"
ContentTemplateSelector="{StaticResource MyTemplateSelector}" />
I put question marks for the ConverterParameter because I'm not clear on how you determine which case calls for which Width/Height. But you can pass in a parameter that will allow you to decide which value to pass back, and based on that decision, the Converter can determine whether to get the ActualWidth/ActualHeight or the Width/Height of the ScrollViewer that is passed in.

How to access and provide default values to controls within WPF templates?

I'm struggling with control templates. I'm currently building a UI which has several panes which are essentially build out of more basic controls.
here's how one of our construction panes looks like right now:
<Grid>
<StackPanel>
<ContentControl Template="{StaticResource ConstructionBorderCtrl}">
<ContentControl Template="{StaticResource StringCtrl}" Content="Cash Event Value:"/>
</ContentControl>
<ContentControl Template="{StaticResource ConstructionBorderCtrl}">
<ContentControl Template="{StaticResource RateCtrl}"></ContentControl>
</ContentControl>
<ContentControl Template="{StaticResource ConstructionBorderCtrl}">
<ContentControl Grid.Row="0" Template="{StaticResource FromCtrl}"></ContentControl>
</ContentControl>
<ContentControl Template="{StaticResource ConstructionBorderCtrl}">
<ContentControl Grid.Row="0" Template="{StaticResource StartEndDateCtrl}"></ContentControl>
</ContentControl>
<ContentControl Template="{StaticResource ConstructionBorderCtrl}">
<ContentControl Grid.Row="0" Template="{StaticResource ComboStringCtrl}">Applicable Size:</ContentControl>
</ContentControl>
</StackPanel>
</Grid>
Here's a template for the StringCtrl as an example:
<ControlTemplate x:Key="StringCtrl" TargetType="ContentControl">
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Name="ctrlText" Margin="0,0,5,0" Text="{TemplateBinding Content}"></TextBlock>
<TextBox Name="ctrlDefaultValue" Grid.Column="1" />
</Grid>
</ControlTemplate>
As you can see from the template it's really just a label and a textbox. Now let's say I wanted to provide a default value to the text box as well as perform validation on user input, but I want to provide that context from the parent Construction pane and bind it to the individual elements inside the templates. How would I go about doing that?
This is certainly a design I've never seen before. I'd think UserControls or some other type of custom control would work better for this than the ControlTemplate approach.
But if you definitely want to go down this route, I could maybe see a Behavior working for you if there's some consistency to your structure/naming in the templates - you can set properties on the behavior and the behavior can access the control via its AssociatedObject property to be able to set the values of the children and do validation.
Seems like a lot of work to me though.

Categories