I would like to be able to click on a Button named "More" then I will change the current UserControl with another detailed one in the MainWindow.
I tried to make the following but the items in leftmenubar don't work anymore when I run the last line of the code below:
private void btnMore_Click(object sender, RoutedEventArgs e)
{
CarDetailsViewModel c = new CarDetailsViewModel();
(this.Parent as ContentControl).Content = new CarDetails { DataContext = c };
}
I am using http://materialdesigninxaml.net/
The best way to do this would be like so:
<ContentControl>
<ContentControl.ContentTemplate>
<DataTemplate>
<local:CarDetails />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
Code behind:
private void btnMore_Click(object sender, RoutedEventArgs e)
{
CarDetailsViewModel c = new CarDetailsViewModel();
(this.Parent as ContentControl).Content = c;
}
Ideally, the car details viewmodel should be a property of a parent viewmodel bound to the content control's Content:
<ContentControl
Content="{Binding SelectedCarDetails}"
>
<ContentControl.ContentTemplate>
<DataTemplate>
<local:CarDetails />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
SelectedCarDetails would be updated by the parent viewmodel, and/or by selection in a ListBox or ListView, or whatever.
If you want to replace with a usercontrol of a different type, consider using implicit data templates:
<ContentControl>
<ContentControl.Resources>
<DataTemplate DataType="{x:Type models:CarDetailsViewModel}">
<local:CarDetails />
</DataTemplate>
<DataTemplate DataType="{x:Type models:BikeDetailsViewModel}">
<local:BikeDetails />
</DataTemplate>
<DataTemplate DataType="{x:Type models:BoatDetailsViewModel}">
<local:BoatDetails />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
Those could be defined in the UserControl's Resources, or in the Resources of any parent/grandparent/etc. element in the same XAML file. If you give it a Content of any of those viewmodel types, it'll automagically use the corresponding datatemplate.
Related
I have a main View and corresponding ViewModel. Because the main View is too complicated so I split it into many small Views; and each small View also has its own ViewModel.
My question is that how to "associating a sub ViewModel to a sub View" in the main View?
I am doing the following way, not sure if it is right or I have to use DataTemplate?
<StackPanel>
<local:SmallView-A DataContext="{x:Type local:SmallViewModel-A}" />
</StackPanel>
<StackPanel>
<local:SmallView-B DataContext="{x:Type local:SmallViewModel-B}" />
</StackPanel>
<StackPanel>
<local:SmallView-C DataContext="{x:Type local:SmallViewModel-C}" />
</StackPanel>
I would saif that the right anwser is the following :-)... (not tested!)
in a generic.xaml, associated views and viewmodels
In your MainViewModel create properties for each sub viewmodel
In your MainView, bind content presenter to there properties
generic
<DataTemplate DataType="{x:Type ViewModels:SubViewModel1}">
<Views:SubView1 />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:SubViewModel2}">
<Views:SubView2 />
</DataTemplate>
MainViewModel
public SubViewModel1 Sub1 { get; set; }
public SubViewModel2 Sub2 { get; set; }
MainView
<Grid>
## your layout here
<ContentPresenter Content="{Binding Sub1}" />
<ContentPresenter Content="{Binding Sub2}" />
##could be
<TabControl ItemSource="{Binding MyViewModelCollection}" />
</Grid>
It turns out to use the following code likely.
<ContentControl Content="{Binding MyViewInstance}">
<ContentControl.Resources>
<DataTemplate DataType="x:Type vm:MyViewModel">
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
In your 'Views' folder(Create one if you have not got one already) create a new user control called `SmallViewA'.
In your main XAML:
xmlns:views = "clr-namespace:[Your app name].Views"
<views:SmallViewA x:Name = "vSmallViewA" Loaded = "SmallViewALoaded"/>
private void SmallViewALoaded(object sender, RoutedEventArgs e)
{
vSmallViewA.DataContext = YoursmallAViewModelshouldgohere;
// Initialise
}
Add your SmallViewA control the the SmallViewA control in the Views folder. From there you can bind your properties from your SmallViewA Viewmodel.
Follow the same procedure above for SmallViewB and SmallViewC
Note: be sure to implement the INotifyPropertyChanged to your ViewModel.
XAML:
<ItemsControl ItemsSource="{Binding Messages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Views:Message110FirstView DataContext="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
VIEWMODEL:
public ObservableCollection<ViewModelBase> Messages
{
get { return GetValue<ObservableCollection<ViewModelBase>>(MessagesProperty); }
set { SetValue(MessagesProperty, value); }
}
public static readonly PropertyData MessagesProperty = RegisterProperty("Messages", typeof(ObservableCollection<ViewModelBase>), null);
My question relates to this part of xaml:
<Views:Message110FirstView DataContext="{Binding}"/>
So, how to make different views in this place.
Thank you.
If I understand you correctly, then you want change view based in viewmodel.
It is appropriate to use DataTemplates if you want to dynamically switch Views depending on the ViewModel:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModelA}">
<localControls:ViewAUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModelB}">
<localControls:ViewBUserControl/>
</DataTemplate>
<Window.Resources>
<ContentPresenter Content="{Binding CurrentView}"/>
</Window>
If Window.DataContext is an instance of ViewModelA, then ViewA will be displayed and Window.DataContext is an instance of ViewModelB, then ViewB will be displayed.
The best example I've ever seen and read it is made by Rachel Lim. See the example.
I want to do with xaml bindings such feature:
Listbox contains hyperlinks.
When hyperlink clicked - we go to another frame
But also SelectedItem must changed, and on another frame we show info about selected item.
I want it without subscribing click/selected events. Only declarative
Example of my listbox
<ListBox Grid.Row="1"
x:Name="OrderTypesListBox"
ItemsSource="{Binding OrderTypes, Mode=OneWay}"
SelectedItem="{Binding SelectedCall.OrderType, Mode=TwoWay}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<HyperlinkButton Style="{StaticResource LinkStyle}" NavigateUri="/WindowPage" TargetName="ContentFrame" Content="WindowPage"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now solve like that
<ListBox Grid.Row="1"
x:Name="OrderTypesListBox"
ItemsSource="{Binding OrderTypes, Mode=OneWay}"
SelectedItem="{Binding SelectedCall.OrderType, Mode=TwoWay}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<HyperlinkButton
TargetName="ContentFrame"
NavigateUri="{Binding OrderTypeNextPage}"
Content="{Binding Name}"
Click="HyperlinkButton_Click"
Tag="{Binding}"
/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
private void HyperlinkButton_Click(object sender, RoutedEventArgs e)
{
OrderTypesListBox.SelectedItem = (sender as HyperlinkButton).Tag;
}
Don't use a HyperlinkButton. Perform the needed actions when the SelectedItem changes in your ViewModel.
Edit: If you need to respond to all click events even if the item is already selected then you can use a Behavior to do this. Just create a behavior for the TextBlock that navigates on the TextBlock click event.
Edit2: Behaviors are pretty simple to code up and easy to use (and don't break the MVVM paradigm).
public class NavigatingTextBlockBehavior : Behavior<TextBlock>
{
protected override void OnAttached()
{
AssociatedObject.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseDown);
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
NavigationService.Navigate(new Uri("/WindowPage"));
}
}
I'm trying to use DataBinding for dynamically populating a TabControl but have a problem. dataBinding runs fine but I would like the content of each TabItem to be independent one from the other. Here is my XAML code:
<TabControl
DockPanel.Dock="Left"
ItemsSource="{Binding OpenChats}"
Name="tabChats"
VerticalAlignment="Top"
Width="571">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
TabItems are created with different headers (as I want) but when the user types something in the TextBox inside the ContentTemplate, the same text is maintained in different tabItems and I don't want this.
What am I doing wrong?
I had same problem. This answer helped me. My solution was to remove focus from textbox when tab changed. When focus from textbox is removed, new content is set to binded property as expected.
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DependencyObject focusedElement = (FocusManager.GetFocusedElement(tabControl) as DependencyObject);
if (focusedElement != null)
{
DependencyObject ancestor = VisualTreeHelper.GetParent(focusedElement);
while (ancestor != null)
{
var element = ancestor as UIElement;
if (element != null && element.Focusable)
{
element.Focus();
break;
}
ancestor = VisualTreeHelper.GetParent(ancestor);
}
}
}
or use
Text="{Binding UpdateSourceTrigger=PropertyChanged}"
on textbox binding.
The TextBox in the ContentTemplate has no Binding. Try
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox Text="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
Adjust the bindingpath if necessary
In my MVVM app I have a treeview representing records in a database. My views and viewmodels are linked in a resource dictionary like this
<DataTemplate DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
I want to display a preview of the view when a user hovers over an icon using the tooltip. My HierarchicalDataTemplate in the treeview is this
<HierarchicalDataTemplate DataType="{x:Type vm:TrialSiteViewModel}"
ItemsSource="{Binding Path=Children}">
...
<Button Style="{StaticResource previewButtonStyle}">
<Button.ToolTip>
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>
</ToolTip>
</Button.ToolTip>
</Button>
</StackPanel>
</HierarchicalDataTemplate>
This correctly picks up the TrialSiteViewModel that is the DataContext for the Treeviewitem.
ObjectPreview uses a viewbox and contentcontrol to display the view of the record
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}">
</ContentControl>
</Viewbox>
and the code behind contains the dependency property
public partial class ObjectPreview : UserControl
{
public ObjectPreview()
{
InitializeComponent();
}
public static readonly DependencyProperty _previewObjectProperty =
DependencyProperty.Register("PreviewObject", typeof(TreeViewItemViewModel), typeof(ObjectPreview));
public TreeViewItemViewModel PreviewObject
{
get { return (TreeViewItemViewModel)GetValue(_previewObjectProperty); }
set { SetValue(_previewObjectProperty, value); }
}
}
The problem I'm having is that the Template used to display the object is the same as that used in the treeview. This simply shows an icon and an object summary (ie. Primary Key and one or two key fields) rather than the entire template as defined in the view TrialSiteView. If I amend the code to use a button Command on the TrialSiteViewModel and inject it into ObjectPreview I can set the contentcontrol in the code behind and the TrialSiteView is used.
I'm guessing that somehow the Template is inferred from the TreeViewItem. Can anyone tell me how I can ensure the tooltip uses the TrialSiteView?
UPDATE
Ok, so I've fixed this but had to resort to code behind and removed the usercontrol and put the view directly in the tooltip. The key bit is getting the datatemplate from the resources. I'd tried to do this previously by assigning a key to the datatemplate, but either my code was flawed or it did not work. Anyhow, this works but is not the preferred Xaml solution.
private void PreviewObject_MouseEnter(object sender, MouseEventArgs e)
{
Image image = (Image)sender;
var key = new System.Windows.DataTemplateKey(image.DataContext.GetType());
var datatemplate = (DataTemplate)this.FindResource(key);
ToolTip tooltip = new ToolTip();
tooltip.Style = VisualUtils.GetResource<Style>("ControlTemplates.xaml", "toolTipWithContentStyle");
tooltip.MaxWidth = 460;
ContentControl contentcontrol = new ContentControl();
contentcontrol.ContentTemplate = datatemplate;
contentcontrol.Content = image.DataContext as TreeViewItemViewModel;
Viewbox viewbox = new Viewbox();
viewbox.Stretch = Stretch.Uniform;
viewbox.Child = contentcontrol;
tooltip.Content = viewbox;
image.ToolTip = tooltip;
}
What you need to do is to specify explicitly what data template to use. In order to do that just add a template property along with the PreviewObject property in the preview control:
public static readonly DependencyProperty _previewObjectTemplateProperty =
DependencyProperty.Register("PreviewObjectTemplate", typeof(DataTemplate), typeof(ObjectPreview));
public DataTemplate PreviewObjectTemplate
{
get { return (DataTemplate)GetValue(_previewObjectTemplateProperty); }
set { SetValue(_previewObjectTemplateProperty, value); }
}
Then, in the ObjectPreview.xaml add the ContentTemplate property that is bound to the PreviewObjectTemplate property:
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}"
ContentTemplate="{Binding PreviewObjectTemplate}" >
</ContentControl>
</Viewbox>
And finally, give a key to your data template and specify a reference to it explicitly when you declare ObjectPreview:
<DataTemplate x:Key="FullViewTemplate" DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
...
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
PreviewObjectTemplate="{StaticResource FullViewTemplate}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>