I'm writing an application in WPF using the MVVM pattern. In my application I've got an IPopupWindowService which I use to create a popup dialog window.
So to show a ViewModel in a popup window you'd do something like this:
var container = ServiceLocator.Current.GetInstance<IUnityContainer>();
var popupService = container.Resolve<IPopupWindowService>();
var myViewModel = container.Resolve<IMyViewModel>();
popupService.Show((ViewModelBase)myViewModel);
This is all well and good. What I want to do is be able to set the MinHeight and MinWidth on the View bound to myViewModel and have the popup window use those settings so that a user cannot make the window smaller than its contents will allow. At the moment when the user shrinks the window the contents stops resizing but the window doesn't.
EDIT:
I map my Views to my ViewModels in ResourceDictionarys like so:
<DataTemplate DataType="{x:Type ViewModels:MyViewModel}">
<Views:MyView />
</DataTemplate>
And my popup view looks like this:
<Window x:Class="TheCompany.Cubit.Shell.Views.PopupWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterOwner">
<DockPanel x:Name="panelContent">
<StackPanel HorizontalAlignment="Right" DockPanel.Dock="Bottom" Orientation="Horizontal" Visibility="{Binding RelativeSource={RelativeSource AncestorType=Window},Path=ButtonPanelVisibility}">
<Button Width="75" IsDefault="True" x:Uid="ViewDialog_AcceptButton" Click="OnAcceptButtonClick" Margin="4">OK</Button>
<Button Width="75" IsCancel="True" x:Uid="ViewDialog_CancelButton" Click="OnCancelButtonClick" Margin="0,4,4,4">Cancel</Button>
</StackPanel>
<ContentPresenter Content="{Binding}" />
</DockPanel>
You can define MinHeight and MinWidth properties on your ViewModel and use databinding to bind the View to those properties in XAML:
<...
MinHeight="{Binding Path=MinHeight}"
MinWidth="{Binding Path=MinWidth}"
.../>
I designed exactly the same generic modal dialog control (using Type-targeted DataTemplates) and also stumbled into this problem.
Using a RelativeSource does not work because you can only find ancestors that way (afaik).
Another possible solution was to name the ContentPresenter and bind to properties on that using ElementName binding. However, the ContentPresenter does not "inherit" the MinHeight and MinWidth properties from the Visual child it renders.
The solution eventually was to use VisualTreeHelper to get at the resolved View associated with the ViewModel at runtime:
DependencyObject lObj = VisualTreeHelper.GetChild(this.WindowContent, 0);
if (lObj != null && lObj is FrameworkElement)
{
lWindowContentMinHeight = ((FrameworkElement)lObj).MinHeight;
lWindowContentMinWidth = ((FrameworkElement)lObj).MinWidth;
}
I put this code in a OnActivated() override in the code-behind of the ModalDialogView (in OnInitialized the View can't be resolved yet).
The only remaining issue is to correct these minimums so that the window width and button panel height is taken into account.
UPDATE
Below is the code I use in my generic modal dialog. It solves the following additional problems:
It centers on the owner window correctly
It doesn't do anything if the content doesn't have MinWidth and MinHeight set
private bool _MinSizeSet = false;
public ModalDialogView(object pDataContext)
: this()
{
this.DataContext = pDataContext;
this.LayoutUpdated += new EventHandler(ModalDialogView_LayoutUpdated);
}
void ModalDialogView_LayoutUpdated(object sender, EventArgs e)
{
if (System.Windows.Media.VisualTreeHelper.GetChildrenCount(this.WindowContent) > 0)
SetInitialAndMinimumSize();
}
private void SetInitialAndMinimumSize()
{
FrameworkElement lElement = VisualTreeHelper.GetChild(this.WindowContent, 0) as FrameworkElement;
if (!_MinSizeSet && lElement != null)
{
if (lElement.MinWidth != 0 && lElement.MinHeight != 0)
{
double lHeightDiff = this.ActualHeight - this.WindowContent.ActualHeight;
double lWidthDiff = this.ActualWidth - this.WindowContent.ActualWidth;
this.MinHeight = lElement.MinHeight + lHeightDiff;
this.MinWidth = lElement.MinWidth + lWidthDiff;
this.SizeToContent = SizeToContent.Manual;
this.Height = this.MinHeight;
this.Width = this.MinWidth;
this.Left = this.Owner.Left + (this.Owner.ActualWidth - this.ActualWidth) / 2;
this.Top = this.Owner.Top + (this.Owner.ActualHeight - this.ActualHeight) / 2;
}
_MinSizeSet = true;
}
}
Related
I have a simple custom panel that is hosted within the ItemsPanel of an ItemsControl. The Template of the ItemsControl is updated to surround the ItemsPresenter with a Viewbox and a ScrollViewer. Here is the XAML code:
<ItemsControl ItemsSource="{Binding Buttons, RelativeSource={RelativeSource
AncestorType={x:Type Local:MainWindow}}}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.CanContentScroll="True">
<ItemsControl.Template>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<ScrollViewer CanContentScroll="True">
<Viewbox Stretch="UniformToFill">
<ItemsPresenter />
</Viewbox>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Local:TestPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Height="250" Width="250" HorizontalAlignment="Center"
Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And the Panel:
public class TestPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
var desiredSize = new Size();
var layoutSlotSize = availableSize;
layoutSlotSize.Height = double.PositiveInfinity;
for (int i = 0, count = InternalChildren.Count; i < count; ++i)
{
UIElement child = InternalChildren[i];
if (child == null) continue;
child.Measure(layoutSlotSize);
var childDesiredSize = child.DesiredSize;
desiredSize.Width = Math.Max(desiredSize.Width, childDesiredSize.Width);
desiredSize.Height += childDesiredSize.Height;
}
return desiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
double verticalOffset = 0;
for (int i = 0, count = InternalChildren.Count; i < count; ++i)
{
UIElement child = InternalChildren[i];
if (child == null) continue;
child.Arrange(new Rect(0, verticalOffset, child.DesiredSize.Width,
child.DesiredSize.Height));
verticalOffset += child.DesiredSize.Height;
}
return base.ArrangeOverride(finalSize);
}
}
And finally, MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Buttons = new ObservableCollection<string>();
IEnumerable<int> characterCodes = Enumerable.Range(65, 26);
foreach (int characterCode in characterCodes)
Buttons.Add(((char)characterCode).ToString().ToUpper());
}
public static readonly DependencyProperty ButtonsProperty =
DependencyProperty.Register(nameof(Buttons), typeof(ObservableCollection<string>),
typeof(MainWindow), null);
public ObservableCollection<string> Buttons
{
get { return (ObservableCollection<string>)GetValue(ButtonsProperty); }
set { SetValue(ButtonsProperty, value); }
}
}
This all works as expected... so far, so good. The problem is when I change the base class from Panel to VirtualizingPanel, which I need to do to virtualise the data (not this example button data). After changing the base class, the panel immediately stops working. I am totally aware of how to virtualize data in a panel... I have a working example of this. My problem is when I want to put add a Viewbox inside the ScrollViewer.
Please note that this XAML will work fine with a normal Panel, or StackPanel, but as soon as I change it to VirtualizingPanel, it stops working (nothing is rendered, and the InternalChildren property contains no elements). Can anyone shed some light on this problem for me please?
I still do not know why the VirtualizingPanel does not work within a ViewBox within a ScrollViewer, but I have discovered that if I extend the VirtualizingStackPanel class in my panel instead, everything works as expected.
Therefore, the solution for those who require virtualized items to be stacked is to extend the VirtualizingStackPanel class instead. For those who need other types of child arrangement, I'm sorry, but I have no answer, unless you remove the Viewbox.
I would still be more than happy to receive any further information on this subject.
Iam displaying messages in my WPF application
when a new message is added to the messages, i need to highlight it.so i want to dynamically get the text added to TextBlock
i have the xaml like this
<ItemsControl Name="DialogItemsControl" ItemsSource="{Binding Messages, Mode=OneWay}" Background="Transparent"
BorderBrush="Transparent" TargetUpdated="DialogItemsControl_TargetUpdated">
<ItemsControl.ItemTemplate><!-- For ever message -->
<DataTemplate>
<Grid Margin="0,0,0,20">
<ItemsControl Name="SubDialogItemsControl"
Foreground="{DynamicResource ButtonTextBrush}"
ItemsSource="{Binding Lines,NotifyOnTargetUpdated=True}"
Margin="0,0,0,12"
Grid.Column="0">
<ItemsControl.ItemTemplate><!-- For every line -->
<DataTemplate>
<TextBlock Name="DialogMessageText"
Text="{Binding NotifyOnTargetUpdated=True}"
VerticalAlignment="Top"
Margin="0,2,0,2"
TextTrimming="WordEllipsis"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and the code in the codebehind class is like this:
private void DialogItemsControl_TargetUpdated(object sender, System.Windows.Data.DataTransferEventArgs e)
{
ItemsControl itemControl = sender as ItemsControl;
ContentPresenter dp = itemControl.ItemContainerGenerator.ContainerFromItem(itemControl.Items.CurrentItem) as ContentPresenter;
// Finding textBlock from the DataTemplate that is set on that ContentPresenter
DataTemplate myDataTemplate = dp.ContentTemplate;
ItemsControl itc = (ItemsControl)myDataTemplate.FindName("SubDialogItemsControl", dp);
if (itc != null && itc.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
{
ContentPresenter cp = itc.ItemContainerGenerator.ContainerFromIndex(0) as ContentPresenter;
DataTemplate dt = cp.ContentTemplate;
TextBlock tb = dt.LoadContent() as TextBlock;
tb.TargetUpdated += new EventHandler<System.Windows.Data.DataTransferEventArgs>(myTextBlock_TargetUpdated);
}
}
void myTextBlock_TargetUpdated(object sender, System.Windows.Data.DataTransferEventArgs e)
{
TextBlock tb = sender as TextBlock;
//When i access the text property of tb, its showing null, how to get the text
}
When i access the text property of textblock in the target updated event of textblock, its showing null, how to read the text.
Thanks in advance
You tackle the problem from the wrong angle (and probably add a memory leak in the process since I don't see you unsubscribing to the event).
You need to create a Custom TextBlock, overriding the metadata of the Text property so that it changes the Background for a few seconds when the text string changes (through PropertyChangedCallback).
And then use that custom TextBlock in the DataTemplate of your ItemsControl.
EDIT - I thought other people could need this feature so here is a working example:
public class CustomTextBlock : TextBlock
{
static CustomTextBlock()
{
TextProperty.OverrideMetadata(typeof(CustomTextBlock), new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(
(dpo, dpce) =>
{
//Flash the background to yellow for 2 seconds
var myTxtblk = dpo as CustomTextBlock;
if (myTxtblk != null)
{
myTxtblk.Background = Brushes.Yellow;
Task.Factory.StartNew(
() =>
{
Thread.Sleep(2000);
Application.Current.Dispatcher.Invoke(
new Action(() =>
{
myTxtblk.Background = Brushes.Transparent;
}));
});
}
})));
}
}
Then you need to declare the right xmlns namespace in your XAML view, and you use it like a regular TextBlock:
<local:CustomTextBlock Text="{Binding MyDynamicText}"/>
It will flash yellow when MyDynamicText is modified (provided it raises PropertyChanged).
I have a WPF ListView that should be extended with an always visible footer.
The footer shall behave like a header and should not be scrolled away.
The following XAML uses an external ScrollViewer linked to code behind to steer the ScrollViewer of the ListView:
<Window x:Class="LayoutTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="125" Width="176">
<Grid>
<StackPanel>
<ListView Name="L" ScrollViewer.HorizontalScrollBarVisibility="Hidden">
<ListViewItem Content="Brown brownie with a preference for white wheat."/>
<ListViewItem Content="Red Redish with a taste for oliv olives."/>
</ListView>
<ScrollViewer CanContentScroll="False" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" ScrollChanged="ScrollViewer_ScrollChanged">
<!-- Would like to bind Rectangle.Width to the preferred width of L -->
<Rectangle Height="20" Width="500" Fill="Red"/>
</ScrollViewer>
</StackPanel>
</Grid>
</Window>
In the code behind this looks like this:
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
var bottomScrollViewer = sender as ScrollViewer;
var listScrollViewer = GetScrollViewer(L) as ScrollViewer;
if (listScrollViewer != null && bottomScrollViewer != null )
listScrollViewer.ScrollToHorizontalOffset( bottomScrollViewer.HorizontalOffset );
}
GetScrollViewer() is defined like this (but unimportant):
public static DependencyObject GetScrollViewer(DependencyObject depObj)
{
if (depObj is ScrollViewer)
{ return depObj; }
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = GetScrollViewer(child);
if (result == null) { continue; }
else { return result; }
}
return null;
}
The ScrollViewer of ListView obviously knows about the preferred width of its children.
The problem is that I cant find a way to bind to that width. So here it is:
How do I bind Rectangle.Width to the preferred size of the ListView?
Or, alternatively, how do I include a footer in the ListView that is always visible?
You need to bind against ExtentWidth of your ScrollViewer. According to http://msdn.microsoft.com/en-us/library/system.windows.controls.scrollviewer.extentwidth.aspx, it's a DependencyProperty. Mind that you need the ScrollViewer of your ListView, not the additional one you are creating below the list view.
You can use your GetScrollViewer function to find the ScrollViewer on the ListView. Of course, you'll need to set the binding in the code-behind. Something like that:
Binding b = new Binding("ExtentWidth") { Source = GetScrollViewer(L) };
rect.SetBinding(Rectangle.WidthProperty, b);
I have a Silverlight application that is using a DataGrid. Inside of that DataGrid I have a DataTemplate that is defined like the following:
<Grid x:Name="myGrid" Tag="{Binding}" Loaded="myGrid_Loaded">
<ItemsControl ItemsSource="{Binding MyItems}" Tag="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Horizontal" Width="138">
<TextBlock Text="{Binding Type}" />
<TextBox x:Name="myTextBox" TextChanged="myTextBox_TextChanged" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
When a user enters text into the TextBox, I have an event (myTextBox_TextChanged) that must be fired at this point. When that event gets fired, I would like to get the ItemsControl element that is the container for this TextBox. How do I get that ItemsControl from my event handler?
Please note: Because the ItemsControl is in the DataTemplate of DataGrid, I don't believe I can just add an x:Name and reference it from my code-behind. Or is there a way to do that?
Thank you!
Using a combination of ItemsControl.ItemsControlFromItemContainer and VisualTreeHelper.GetParent you should be able to find your ItemsControl
var txt = sender as TextBox;
var panel1 = VisualTreeHelper.GetParent(txt);
var panel2 = VisualTreeHelper.GetParent(panel1);
var contentPresenter = VisualTreeHelper.GetParent(panel2);
var ic = ItemsControl.ItemsControlFromItemContainer(contentPresenter);
You may also want search the web for VisualTreeHelper Recursive functions to make some of this easier.
I like to have this little extension method in a static class somewhere in my app:-
public static IEnumerable<DependencyObject> Ancestors(this DependencyObject root)
{
DependencyObject current = VisualTreeHelper.GetParent(root);
while (current != null)
{
yield return current;
current = VisualTreeHelper.GetParent(current);
}
}
With that you should be able to do something like this:-
ItemsControl control = ((DependencyObject)sender).Ancestors()
.TypeOf<ItemsControl>().FirstOrDefault();
Not sure if this applies but this creates a "toggling button bar" using the same principles.
private void UIClassButton_Click(object sender, RoutedEventArgs e){
Button SenderButton = (Button)sender;
ItemsControl SendersItemControl = ItemsControl.ItemsControlFromItemContainer(VisualTreeHelper.GetParent(SenderButton));
IEnumerable<DependencyObject> DependencyObjectCollection = SendersItemControl.GetContainers();
foreach (ContentPresenter item in DependencyObjectCollection) {
ContentPresenter UIClassPresenter = (ContentPresenter)item;
Button UIClassButton = (Button)UIClassPresenter.GetVisualChildren().First();
if (UIClassButton != SenderButton) {
VisualStateManager.GoToState(UIClassButton, "Normal", true);
}
else {
VisualStateManager.GoToState(UIClassButton, "Pressed", true);
}
}
}
Here's an example of capturing a container that houses your ItemsControl's item:
CheckBox checkbox = sender as CheckBox;
foreach (var item in MembersItemsControl.Items)
{
var container = MembersItemsControl.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
UserInformation user = container.DataContext as UserInformation;
bool isMale = true;
if (user.sex == isMale && checkbox.IsChecked.Value == true)
{
container.Visibility = System.Windows.Visibility.Visible;
}
}
I hope that helps.
Scenario: I have a ListBox and the ListBoxItems have a DataTemplate. What I want to do is put a ContextMenu in the DataTemplate. The catch is that I want this ContextMenu ItemsSource to be different depending on certain properties in the window. My initial thought is that I could just bind the ItemsSource to a Property in the window and that would return an ItemsSource; however, I cant seem to bind to this property correctly. I believe this is because I am in the DataTemplate and consequently the DataContext (I believe that is the right word) is of that ListBoxItem and not of the window.
How could I get the ContextMenu that is inside a DataTemplate to bind to a Property outside of the DataTemplate.
You can get the DataContext from your window by using the RelativeSource FindAncestor syntax
<DataTemplate>
<TextBlock Text="{Binding MyInfo}">
<TextBlock.ContextMenu>
<Menu ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.MyContextMenuItems}"/>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
Not totally sure, but the binding is correct...
If your DataContext is on another object type, you just have to change the AncestorType (eg. by UserControl).
This might be a good candidate for an AttachedProperty. Basically what you would do is wrap your ContextMenu in a UserControl and then add a Dependency Property to the UserControl. For example:
MyContextMenu.xaml
<UserControl x:Class="MyContextMenu" ...>
<UserControl.Template>
<ContextMenu ItemSource="{Binding}" />
</UserControl.Template>
</UserControl>
MyContextMenu.xaml.cs
public static readonly DependencyProperty MenuItemsSourceProperty = DependencyProperty.RegisterAttached(
"MenuItemsSource",
typeof(Object),
typeof(MyContextMenu),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender)
);
public static void SetMenuItemsSource(UIElement element, Boolean value)
{
element.SetValue(MenuItemsSourceProperty, value);
// assuming you want to change the context menu when the mouse is over an element.
// use can use other events. ie right mouse button down if its a right click menu.
// you may see a perf hit as your changing the datacontext on every mousenter.
element.MouseEnter += (s, e) => {
// find your ContextMenu and set the DataContext to value
var window = element.GetRoot();
var menu = window.GetVisuals().OfType<MyContextMenu>().FirstOrDefault();
if (menu != null)
menu.DataContext = value;
}
}
public static Object GetMenuItemsSource(UIElement element)
{
return element.GetValue(MenuItemsSourceProperty);
}
Window1.xaml
<Window ...>
<Window.Resources>
<DataTemplate TargetType="ListViewItem">
<Border MyContextMenu.MenuItemsSource="{Binding Orders}">
<!-- Others -->
<Border>
</DataTemplate>
</Window.Resources>
<local:MyContextMenu />
<Button MyContextMenu.MenuItemsSource="{StaticResource buttonItems}" />
<ListView ... />
</Window>
VisualTreeHelpers
public static IEnumerable<DependencyObject> GetVisuals(this DependencyObject root)
{
int count = VisualTreeHelper.GetChildrenCount(root);
for (int i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(root, i);
yield return child;
foreach (var descendants in child.GetVisuals())
{
yield return descendants;
}
}
}
public static DependencyObject GetRoot(this DependencyObject child)
{
var parent = VisualTreeHelper.GetParent(child)
if (parent == null)
return child;
return parent.GetRoot();
}
This example is un-tested I'll take a look later tonight and make sure its accurate.