WPF GridSplitter visiblity - wpf

I have a problem regarding GridSplitter visiblity.
In this, whatever I am hosting a Winform DataGridView. The GridSplitter, when dragged is properly visible on other controls. But not on this grid. In fact, whatever I host instead of Datagridview, becomes the topmost control, which makes the GridSplitter hide behind it.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Name="rowForButton"/>
<RowDefinition Name="rowForGridSplitter" Height="Auto" MinHeight="81" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Height="50" Width="110" Content="Button in First Row"/>
<my:WindowsFormsHost Panel.ZIndex="0" Grid.Row="1" Margin="30,11,138,0" x:Name="winHost" Height="58" VerticalAlignment="Top" OpacityMask="Transparent">
<win:DataGridView x:Name="dataGridView"></win:DataGridView>
</my:WindowsFormsHost>
<GridSplitter BorderThickness="1" Panel.ZIndex="1" Grid.Row="1" HorizontalAlignment="Stretch" Height="5" ShowsPreview="True" VerticalAlignment="Top">
</GridSplitter>
</Grid>

Usually you should either put a GridSplitter into its own grid cell or ensure via margins that no control can overlap it. But I don't know whether that exactly applies to you here. See also here.

I encountered this problem also, there is my solution:
var splitter = new GridSplitter()
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
FocusVisualStyle = null,
ShowsPreview = true,
Background = new SolidColorBrush(new Color() { R = 1, G = 1, B = 1, A = 1 }),
};
// non-style / essential window which will display over your WinForm control
var PopupWindowForSplitter = new PopupWindow()
{
Background = new SolidColorBrush(new Color() { R = 1, G = 1, B = 1, A = 1 }),
Visibility = Visibility.Collapsed
};
PopupWindowForSplitter.Show();
...
Point _ptForSplitterDrag = new Point(0,0);
splitter.DragStarted += (o, e) =>
{
var pt = splitter.PointToScreen(new Point());
_ptForSplitterDrag = splitter.PointToScreen(Mouse.GetPosition(splitter));
PopupWindowForSplitter.Left = pt.X;
PopupWindowForSplitter.Top = pt.Y;
PopupWindowForSplitter.Height = splitter.ActualHeight;
PopupWindowForSplitter.Width = splitter.ActualWidth;
PopupWindowForSplitter.Activate();
PopupWindowForSplitter.Visibility = Visibility.Visible;
};
splitter.DragDelta += (o, e) =>
{
var pt = splitter.PointToScreen(Mouse.GetPosition(splitter)) - _ptForSplitterDrag
+ splitter.PointToScreen(new Point());
if (splitter.ResizeDirection == GridResizeDirection.Rows)
{
PopupWindowForSplitter.Top = pt.Y;
}
else
{
PopupWindowForSplitter.Left = pt.X;
}
};
splitter.DragCompleted += (o, e) =>
{
var initializeData = typeof(GridSplitter).GetMethod("InitializeData", BindingFlags.NonPublic | BindingFlags.Instance);
var moveSplitter = typeof(GridSplitter).GetMethod("MoveSplitter", BindingFlags.NonPublic | BindingFlags.Instance);
if (moveSplitter != null && initializeData != null)
{
initializeData.Invoke(splitter, new object[] { true });
var pt = splitter.PointToScreen(Mouse.GetPosition(splitter)) - _ptForSplitterDrag;
if (splitter.ResizeDirection == GridResizeDirection.Rows)
{
moveSplitter.Invoke(splitter, new object[] { 0, pt.Y });
}
else
{
moveSplitter.Invoke(splitter, new object[] { pt.X, 0 });
}
}
PopupWindowForSplitter.Visibility = Visibility.Collapsed;
};
Maybe there are some issues in my description because of my poor english, but I think the code is enough to explain it.

Windows Forms controls are always rendered seperately from your WPF controls, and as a result will always appear over your WPF application.
See Hosting a Microsoft Win32 Window in WPF (subheading Notable Differences in Output Behavior) for more info.

Try using a WPF-native DataGrid control. There are a couple of commercial third-party controls you can buy, or you could take a look at one provided by Microsoft (currently still in CTP):
Xceed DataGrid
Telerik RadGridView
Microsoft DataGrid CTP

In your situation, the quickest fix would be to move the GirdSplitter to the Row with the Button:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Name="rowForButton"/>
<RowDefinition Name="rowForGridSplitter" Height="Auto" MinHeight="81" />
</Grid.RowDefinitions>
<Button Grid.Row="0" Height="50" Width="110" Content="Button in First Row"/>
<my:WindowsFormsHost Panel.ZIndex="0" Grid.Row="1" Margin="30,11,138,0" x:Name="winHost" Height="58" VerticalAlignment="Top" OpacityMask="Transparent">
<win:DataGridView x:Name="dataGridView"></win:DataGridView>
</my:WindowsFormsHost>
<GridSplitter BorderThickness="1" Panel.ZIndex="1" Grid.Row="0" HorizontalAlignment="Stretch" Height="5" ShowsPreview="True" VerticalAlignment="Bottom">
</GridSplitter>
</Grid>
Now just adjust the margins to make sure there is some space between the button and the grid splitter.

The solution would be to add a 'Windows Form' label inside the grid splitter, and to do so programmatically after the addition of the DataGridView so it appears on top of it, as follows:
void AddLabelToSplitter()
{
string template =
#" <ControlTemplate
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:mn='clr-namespace:MyNameSpace;assembly=MyAssembly'
xmlns:wf='clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' TargetType='{x:Type GridSplitter}'>
<mn:ExtendedWindowsFormsHost x:Name='Grid_Splitter_WindowsFormsHost' HorizontalAlignment='Stretch' VerticalAlignment='Stretch'>
<wf:Label Dock='Fill' BackColor='DarkGray'></wf:Label>
</mn:ExtendedWindowsFormsHost>
</ControlTemplate>";
Grid_Splitter.Template = (ControlTemplate)XamlReader.Parse(template);
}
Using a regular windows form host would not work, as it wouldn't pass down the mouse events to the splitter, so use the ExtendedWindowsFormsHost instead from below link:
Keep Mouse Events bubbling from WindowsFormsHost on

Related

WPF ScrollViewer not working in Grid programmatically populated

I have this situation in xaml
<StackPanel Orientation="Vertical" Margin="5" Background="AliceBlue" Height="370">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid Name="DescriptionsGrid" MinHeight="0" MaxHeight="370" ScrollViewer.CanContentScroll="True"></Grid>
</ScrollViewer>
</StackPanel>
and I'm filling the Grid with the following
DescriptionsGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
TextBlock textBlock = new TextBlock { Text = description, TextWrapping = TextWrapping.WrapWithOverflow };
Label label = new Label { Content = textBlock, Foreground = new SolidColorBrush(Colors.Blue) };
label.MouseLeftButtonDown += new MouseButtonEventHandler(LoadXML);
DescriptionsGrid.Children.Add(label);
Grid.SetRow(DescriptionsGrid.Children[DescriptionsGrid.Children.Count - 1], description_counter);
description_counter++;
I can't define rows height as the description may be long enought to wrap the text to new line.
The scrollbar does not appears and new elements go hidden down below.
Any idea?
Following the suggestion of #Clemens I rewrote the code, and found that the mistake was in the XML.
To populate the grid
foreach (HostMessages hostMessage in hostMessagesList)
{
Label message = new Label
{
Content = new TextBlock {
Text = hostMessage.Description,
TextWrapping = TextWrapping.WrapWithOverflow
},
Foreground = new SolidColorBrush(Colors.Blue),
Cursor = Cursors.Hand
};
message.MouseLeftButtonDown += new MouseButtonEventHandler(LoadXML);
itemsControl.Items.Add(message);
}
The XML is as follows
<DockPanel Grid.Row="1" Margin="5" Background="AliceBlue" Height="370">
<ScrollViewer VerticalScrollBarVisibility="Auto" Height="370">
<ItemsControl Name="itemsControl"></ItemsControl>
</ScrollViewer>
</DockPanel>
The mistake was assigning a MaxHeight="370" to the Grid and none to the ScrollViewer.

ListBox throws ItemControl Text Visualizer error when scrolling

I have a listbox, code below, that I'm populating with 900 items. When I scroll down the list quickly I get an exception of 'An ItemsControl is inconsistent with its items source'. Any ideas as to what is causing the issue?
<ListBox x:Name="FileList" ItemsSource="{Binding Files}" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True"
MaxHeight="520" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="FileChecked" IsChecked="{Binding Checked}" Grid.Column="0" BorderThickness="1"/>
<Label Content="{Binding Name}" Grid.Column="1" />
<Label Content="{Binding Source}" Grid.Column="2" Style="{StaticResource FileProperties}" />
<Label Content="{Binding Destination}" Grid.Column="3" Style="{StaticResource FileProperties}"/>
<Label Content="{Binding ArchiveLocation}" Grid.Column="4" Style="{StaticResource FileProperties}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
enter image description here
Here is a video of the issue. https://www.screencast.com/t/YUlp24zoXiG
I believe I've found the problem. I've pasted the problematic code below, so that other visitors can benefit from this answer.
// Your code from GitHub unchanged
private async void Preview_Click(object sender, System.Windows.RoutedEventArgs e)
{
vm.Files = new List<InstrumentFile>();
try
{
for (int i = 0; i < InstrumentList.Items.Count; i++)
{
ListBoxItem lbi = (InstrumentList.ItemContainerGenerator.ContainerFromIndex(i)) as ListBoxItem;
ContentPresenter cp = GetFrameworkElementByName<ContentPresenter>(lbi);
DataTemplate dt = InstrumentList.ItemTemplate;
CheckBox cb = (dt.FindName("InstrumentChecked", cp)) as CheckBox;
if (cb.IsChecked == true)
{
List<InstrumentFile> instrumentFiles = new List<InstrumentFile>();
Instrument instrument = ((Instrument)(InstrumentList.Items[i]));
string[] files = Directory.GetFiles(instrument.FileSource);
foreach (string file in files)
{
FileInfo fi = new FileInfo(file);
instrumentFiles.Add(new InstrumentFile()
{
Name = fi.Name,
Source = instrument.FileSource,
Destination = instrument.Destination,
ArchiveLocation = instrument.ArchiveLocation
});
}
if (string.IsNullOrEmpty(instrument.FileExt) == false)
{
IEnumerable<InstrumentFile> filteredFiles = instrumentFiles.Where(f => f.Name.ToUpper().EndsWith(instrument.FileExt.ToUpper()));
if (filteredFiles.Count() > 0)
vm.Files.AddRange(filteredFiles);
}
else
{
if (instrumentFiles.Count > 0)
vm.Files.AddRange(instrumentFiles);
}
}
}
}
catch (Exception ex)
{
await metroWindow.ShowMessageAsync("Exception Encountered", ex.Message, MessageDialogStyle.Affirmative, Helpers.DialogSettings());
}
FileCount.Content = vm.Files.Count.ToString() + " files";
}
Here, you're initializing the Files property in the view model. This causes the data-binding to be updated to an empty list. There's no problem so far. However, you then add things to Files, but these changes are not propagated to the data-binding system because the list in the view model is not an ObservableCollection.
You can fix this problem in a couple of ways. One type of fix is to set the Files property in the view model after you've created and filled the list. The new code would look like this (abbreviated):
private async void Preview_Click(object sender, System.Windows.RoutedEventArgs e)
{
// Create new variable to store the list
var files = new List<InstrumentFile>();
// You do a bunch of things, but you now add items to files, not to vm.Files
files.AddRange(filteredFiles);
// Finally, change Files
vm.Files = files
}
That last line will raise the PropertyChanged event in the view model to update the data-binding, but will do so with the full list of items.
The second kind of fix is to change the type of Files in your view model to ObservableCollection<InstrumentFile>. Any time you make a change to Files, the correct event will be raised to update the data-binding.

Insert user control in Material Design Dialog WPF

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

Listbox ScrollIntoView doesn't work for variable height items

I have a listbox that various items are added to. When a new item is added to the listbox, I need to scroll that item into view (basically scroll to the bottom).
I've tried the solution from How can I have a ListBox auto-scroll when a new item is added? and also from this blog post
However, neither solutions work because my listbox contains variable height items. If I hack my listbox items templates to have a fixed height instead, then it seems to work. Here is an example of one of my item templates:
<DataTemplate x:Key="StatusMessageTemplate">
<Grid Grid.Column="1" VerticalAlignment="top" Margin="0,5,10,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=MessageText}" HorizontalAlignment="Left" Grid.Row="0" Grid.Column="0" FontWeight="Bold" Foreground="{DynamicResource LightTextColorBrush}"/>
<TextBlock Text="{Binding Path=created_at, StringFormat=t}" Style="{StaticResource Timestamp}" TextWrapping="Wrap" HorizontalAlignment="Right" Grid.Row="0" Grid.Column="1"/>
</Grid>
</DataTemplate>
How can I make the new items scroll into view regardless of their height?
I need to scroll that item into view (basically scroll to the bottom).
ScrollIntoView behaves strange when the Listbox has variable height items.
If the sole purpose is to scroll to the bottom, you can access the Scrollviewer directly and scroll to the maximum possible offset as shown below.
var scrollViewer = GetDescendantByType(ListBoxChats, typeof(ScrollViewer)) as ScrollViewer;
scrollViewer.ScrollToVerticalOffset(Double.MaxValue);
public static Visual GetDescendantByType(Visual element, Type type)
{
if (element == null)
{
return null;
}
if (element.GetType() == type)
{
return element;
}
Visual foundElement = null;
if (element is FrameworkElement)
{
(element as FrameworkElement).ApplyTemplate();
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
Visual visual = VisualTreeHelper.GetChild(element, i) as Visual;
foundElement = GetDescendantByType(visual, type);
if (foundElement != null)
{
break;
}
}
return foundElement;
}
GetDescendantByType is an helper function written by punker76 # another SO post
I think I have found the problem. Variable height items are not calculated until displayed. So I add a timer to call the ScrollIntoView function. But even that didn't work well, so I used the VisualTreeHelper to find the ScrollViewer object and force it to the specific row. Here is the code.
System.Windows.Threading.DispatcherTimer dTimer = new System.Windows.Threading.DispatcherTimer();
dTimer.Interval = new TimeSpan(0, 0, 0, 0, 200); // 200 Milliseconds
dTimer.Tick += new EventHandler(
(seder, ea) =>
{
//Verses.ScrollIntoView(Verses.Items[itemIndex]);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(Verses); i++)
{
DependencyObject depObj = VisualTreeHelper.GetChild(Verses, i);
if (depObj is ScrollViewer)
{
ScrollViewer sv = depObj as ScrollViewer;
sv.ScrollToVerticalOffset(itemIndex); // Zero based index
break;
}
}
dTimer.Stop();
});
dTimer.Start();

Update text in adorner on button click

I have created my custom adorner to cover my main window with a gray canvas alongwith a textblock at center to show some status text while i was working on other window.
What i am currently doing is fetching the required adornerElement(ie Canvas with a textblock) from my resources and passing it to an adorner in my view constructor like this -
ResourceDictionary reportResourceDictionary = App.LoadComponent(new Uri("Resources/ReportResources.xaml", UriKind.Relative)) as ResourceDictionary;
UIElement adornerElement = reportResourceDictionary["RefreshingReportAdorner"] as UIElement;
mainWindowBlockMessageAdorner = new MainWindowBlockMessageAdorner(mainPanel, adornerElement);
But i want to update that text in textblock in some scenarios say if i click on some button in other window but how to update the text dynamically??
Adorner element from Resource file-
<Grid x:Key="RefreshingReportAdorner">
<Rectangle Fill="Gray"
StrokeThickness="1"
Stroke="Gray"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"/>
<Border BorderBrush="Black"
BorderThickness="2"
Background="White"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock i18n:LanguageManager.VisualId="6"
Text="Some Text(Update dynamically)"
Padding="15,10,15,10"/>
</Border>
</Grid>
Let me know if additional code or approach required..
Have you tried to create some model and push it to RefreshingReportAdorner element's DataContext?
Code:
var reportResourceDictionary = App.LoadComponent(new Uri("Resources/ReportResources.xaml", UriKind.Relative)) as ResourceDictionary;
var adornerElement = reportResourceDictionary["RefreshingReportAdorner"] as FrameworkElement;
var model = new Model();
model.MyText = "Initial text";
adornerElement.DataContext = model;
mainWindowBlockMessageAdorner = new MainWindowBlockMessageAdorner(mainPanel, adornerElement);
...
model.MyText = "Text after click";
XAML:
<TextBlock i18n:LanguageManager.VisualId="6"
Text="{Binding MyText}"
Padding="15,10,15,10"/>
Model:
public class Item : INotifyPropertyChanged
{
private string _myText;
public string MyText
{
get
{
return this._myText;
}
set
{
this._myText= value;
this.OnPropertyChanged("MyText");
}
}
}

Resources