具有自定义镶边的WPF窗口在右侧和底部具有不需要的轮廓

u3r8eeie  于 2023-01-14  发布在  其他
关注(0)|答案(7)|浏览(142)

我用Microsoft.Windows.Shell dll创建了一个带有自定义chrome的WPF窗口。下面是代码:

<Style TargetType="Window" x:Key="ChromeLessWindowStyle">
        <Setter Property="shell:WindowChrome.WindowChrome">
            <Setter.Value>
                <shell:WindowChrome
           GlassFrameThickness="0"
          ResizeBorderThickness="5"          
          CornerRadius="5"
          CaptionHeight="30">
                </shell:WindowChrome>
            </Setter.Value>
        </Setter>
        <Setter Property="WindowStyle" Value="None"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Window}">
                    <Grid>
                            <Grid Background="#FF595959" >
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                    <RowDefinition Height="*"/>
                                </Grid.RowDefinitions>
                                <Border Grid.Row="0" Height="30" Background="#FF393939">
                                    <DockPanel LastChildFill="False" Margin="0,1,5,0">
                                        <TextBlock DockPanel.Dock="Left" Style="{DynamicResource {x:Static coreKeys:TextBlockKeys.Default}}" FontWeight="Bold" Text="{TemplateBinding Title}" Margin="10,0,0,0" VerticalAlignment="Center"/>
                                        <!--Buttons-->
                                        <Button DockPanel.Dock="Right" behaviors:WindowCommandBehaviors.IsCloseButton="True" Style="{DynamicResource {x:Static coreKeys:ButtonKeys.Close}}" shell:WindowChrome.IsHitTestVisibleInChrome="True"/>
                                        <Button DockPanel.Dock="Right" behaviors:WindowCommandBehaviors.IsMaximizeButton="True" Style="{DynamicResource {x:Static coreKeys:ButtonKeys.Maximize}}" Visibility="{TemplateBinding WindowState,Converter={StaticResource WindowStateToVisibilityConverter},ConverterParameter=MaximizeButton }" shell:WindowChrome.IsHitTestVisibleInChrome="True" />
                                        <Button DockPanel.Dock="Right" behaviors:WindowCommandBehaviors.IsMaximizeButton="True" Style="{DynamicResource {x:Static coreKeys:ButtonKeys.Restore}}"  Visibility="{TemplateBinding WindowState,Converter={StaticResource WindowStateToVisibilityConverter}, ConverterParameter=RestoreButton }" shell:WindowChrome.IsHitTestVisibleInChrome="True" />
                                        <Button DockPanel.Dock="Right" behaviors:WindowCommandBehaviors.IsMinimizeButton="True" Style="{DynamicResource {x:Static coreKeys:ButtonKeys.Minimize}}" shell:WindowChrome.IsHitTestVisibleInChrome="True"/>
                                    </DockPanel>
                                </Border>
                                <ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}"/>
                            </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

这在正常情况下工作得很好,直到我有使用C#代码的窗口的要求,我才发现问题。我有一个消息服务:
1.创建模式窗口。
1.使用WPF用户控件填充其内容。
1.将窗口的数据上下文设置为适当的ViewModel。
1.显示窗口
下面是代码:

var userControl = viewRegistry.GetViewByKey(viewKey_); // Get the UserControl.
var modalWindow = new ModalCustomMessageDialog
{
    // Set the content of the window as the user control
    DataContext = viewModel_,
    // Set the data context of the window as the ViewModel
    Owner = Util.AppMainWindow,
    // Set the owner of the modal window to the app window.
    WindowStartupLocation = WindowStartupLocation.CenterOwner,
    //Title = viewModel.TitleText ?? "",
    ShowInTaskbar = false,
    Content = userControl,
    SizeToContent = SizeToContent.WidthAndHeight
};
if (showAsToolWindow_)
{
    modalWindow.ResizeMode = ResizeMode.NoResize;
    modalWindow.WindowStyle = WindowStyle.ToolWindow;
}
modalWindow.Loaded += modalWindow_Loaded;
modalWindow.Closed += CleanModalWindow;
modalWindow.Show();

注意这条线

SizeToContent = SizeToContent.WidthAndHeight

这会调整窗口的大小以适应用户控件的宽度和高度。这样生成的模态窗口在窗口的右下角有一个黑色的粗轮廓。如下所示:

窗口应该像这样(调整大小后变成):

有几点值得注意:
1.调整窗口大小后,此黑色轮廓将立即消失。
1.如果将SizeToContent设置为SizeToContent.Height或SizeToContent.Width,则不会显示此轮廓。但是,它会分别取消模态窗口的Width或Height。
1.我想窗口重绘可能会有问题,所以我尝试了下面的代码来重绘窗口:

private const int WmPaint = 0x000F;

[DllImport("User32.dll")]
public static extern Int64 SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
 ......................
//Inside the Loaded event handler of the modalWindow
var windowHandle = new WindowInteropHelper(modalWindow).Handle;
SendMessage(windowHandle, WmPaint, IntPtr.Zero, IntPtr.Zero);

这没有效果。
1.这个问题不会出现,如果我有固定的高度和宽度属性给用户控件,填充窗口。但是,我不能总是这样做。
1.消息服务已经存在了很长时间,这个幽灵轮廓最近在自定义镀 chrome 后出现了。
有人遇到过类似的情况吗?任何帮助都将不胜感激。

kninwzqo

kninwzqo1#

我最近在一个包含动态生成元素的窗口上使用自定义窗口镶边时遇到了这个问题。
"为什么会这样“
如果我们使用的是一个静态内容的窗口,窗口可以在初始化时知道包含其子元素所需的最大宽度/高度。
在我们想要使用自动缩放的动态元素的情况下,例如使用MVVM模式的视图,我们需要请求窗口在所有绑定(例如视图)被解析后更新其视觉状态。

解决方案

为了执行我们上面推理的行为,我们需要使用窗口的ContentRendered事件,并将其用于InvalidateVisual()。
在窗口的XAML中,您遇到了问题:

ContentRendered="Window_OnContentRendered"

在代码背后:

private void Window_OnContentRendered(object sender, EventArgs e)
{
    InvalidateVisual();
}
mwkjh3gx

mwkjh3gx2#

我也遇到了同样的问题,并为Window类创建了以下扩展:

public static void FixLayout(this Window window)
{
    bool arrangeRequired = false;
    double deltaWidth = 0;
    double deltaHeight = 0;

    void Window_SourceInitialized(object sender, EventArgs e)
    {
        window.InvalidateMeasure();
        arrangeRequired = true;
        window.SourceInitialized -= Window_SourceInitialized;
    }

    void CalculateDeltaSize()
    {
        deltaWidth = window.ActualWidth - deltaWidth;
        deltaHeight = window.ActualHeight - deltaHeight;
    }

    void Window_LayoutUpdated(object sender, EventArgs e)
    {
        if (arrangeRequired)
        {
            if (window.SizeToContent == SizeToContent.WidthAndHeight)
            {
                CalculateDeltaSize();
            }
            window.Left -= deltaWidth * 0.5;
            window.Top -= deltaHeight * 0.5;
            window.LayoutUpdated -= Window_LayoutUpdated;
        }
        else
        {
            CalculateDeltaSize();
        }
    }

    window.SourceInitialized += Window_SourceInitialized;
    window.LayoutUpdated += Window_LayoutUpdated;
}

让我们看看这段代码的作用。我们为SourceIntializedLayoutUpdated事件创建了两个处理程序。SourceIntialized事件处理程序执行窗口重新测量(删除窗口右边缘和下边缘的黑色条纹)。您可以在此停止,代码如下所示:

public static void FixLayout(this Window window)
{    
    void Window_SourceInitialized(object sender, EventArgs e)
    {
        window.InvalidateMeasure();
        window.SourceInitialized -= Window_SourceInitialized;
    }

    window.SourceInitialized += Window_SourceInitialized;
}

代码的剩余部分负责窗口重新排列。我注意到我的窗口与理想的屏幕中心有一些偏移。这是因为WPF在计算窗口位置时使用了错误的窗口大小。LayoutUpdated事件在SourceInitialized事件发生之前触发了几次(计数取决于SizeToContent属性)。首先,我们计算正确和错误窗口大小之间的差异。在SourceInitialized事件触发之后,执行窗口重新测量并为即将发生的LayoutUpdated事件设置arrangeRequired标志以执行窗口重新排列。然后LayoutUpdated事件处理程序计算最终偏移量(如果SizeToContent属性为WidthAndHeight),并将窗口移到正确的位置,之后窗口不再有黑条。并且它位于屏幕或所有者的中心。此方法应在窗口构造函数中InitializeComponent方法之后调用。

6uxekuva

6uxekuva3#

遇到了同样的问题。作为一种解决方案,我自己在window_loaded-Method中执行了“SizeToContent”:

void Window_Loaded(object sender, RoutedEventArgs e)
{
    Height = outerpanel.DesiredSize.Height + 
             WindowChrome.GetWindowChrome(this).CaptionHeight;
    Width = outerpanel.DesiredSize.Width;
}
7hiiyaii

7hiiyaii4#

你可以在window标签中设置AllowsTransparency=“True”,你可以添加一个OpacityMask。如果你看这个视频的时间是https://www.youtube.com/watch?v=TDOxHx-AMqQ&t=1s,这是下面的一个例子。

<Window ..... 
    xmlns:local="clr-namespace:Your.Project"
    AllowsTransparency="True">
<FrameworkElement.Resources>
    <!-- Set the template style of the HolderForm -->
    <Style TargetType="{x:Type local:YourForm}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Window}">

                    <!-- Outer border with the drop shadow margin -->
                    <Border  Padding="{Binding OuterMarginSize, FallbackValue=0}" BorderBrush="{StaticResource BackgroundDarkBrush}" BorderThickness="{Binding FlatBorderThickness}">

                        <!-- Main window outline -->
                        <Grid>
                            <!-- Corner clipping -->
                            <Grid.OpacityMask>
                                <VisualBrush Visual="{Binding ElementName=OpacityContainer}" />
                            </Grid.OpacityMask>

                            <!-- Opacity mask for corners on grid -->
                            <Border x:Name="OpacityContainer"
                                    Background="Black"
                                    CornerRadius="{Binding WindowCornerRadius, FallbackValue=10}" />

                            <!-- The main window content -->
                            <!-- Page Content -->
                            <Border Padding="{Binding InnerContentPadding}" ClipToBounds="True">
                                <ContentPresenter Content="{TemplateBinding Content}" />
                            </Border>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</FrameworkElement.Resources>

<Grid>
    <!--Your Content-->
</Grid>

</Window>
ndh0cuux

ndh0cuux5#

出于完整性的考虑,MahApps解决这个问题的方法似乎也有效(但它是一个黑客。Paviel's answer似乎更好)。

var window = new Window1();
window.SourceInitialized += (s, e) =>
{
    // Without this workaround,
    // black bars appear at the right and bottom edge of the window.
    var sizeToContent = window.SizeToContent;
    var snapsToDevicePixels = window.SnapsToDevicePixels;
    window.SnapsToDevicePixels = true;
    window.SizeToContent = sizeToContent == SizeToContent.WidthAndHeight ? SizeToContent.Height : SizeToContent.Manual;
    window.SizeToContent = sizeToContent;
    window.SnapsToDevicePixels = snapsToDevicePixels;
};

它显然与调用InvalidateMeasure()具有相同的效果,就像Paviel的回答一样。
只有在ContentRendered上调用InvalidateVisual()(就像在另一个答案中)才会在窗口中留下某种错误的填充。

**编辑:**我发现Paviel对窗口不居中的修复在我的例子中不起作用,事实上,它使情况变得更糟。

下面的改编版本完成了这项工作:

public static void FixLayout(Window window)
{
    bool arrangeRequired = false;
    double widthBeforeFix = 0;
    double heightBeforeFix = 0;

    void Window_SourceInitialized(object sender, EventArgs e)
    {
        widthBeforeFix = window.ActualWidth;
        heightBeforeFix = window.ActualHeight;
        window.InvalidateMeasure();
        arrangeRequired = true;
        window.SourceInitialized -= Window_SourceInitialized;
    }
    void Window_LayoutUpdated(object sender, EventArgs e)
    {
        if (arrangeRequired)
        {
            window.Left += (widthBeforeFix - window.ActualWidth) * 0.5;
            window.Top += (heightBeforeFix - window.ActualHeight) * 0.5;
            window.LayoutUpdated -= Window_LayoutUpdated;
        }
    }

    window.SourceInitialized += Window_SourceInitialized;
    window.LayoutUpdated += Window_LayoutUpdated;
}

在调用InvalidateMeasure()之后,只需计算一次正确的窗口位置,这非常简单:给定固定之前的窗口大小和固定之后的窗口大小,简单地减去差值。

egdjgwm8

egdjgwm86#

有点晚了,但也许有人可以用这个。
我遇到过同样的问题,我通过在ModalCustomMessageDialog中添加宽度和高度作为参数来解决这个问题(我假设它继承了System.Windows.Window)。
就像这样:

public partial class ModalCustomMessageDialog: Window
{

  public ModalCustomMessageDialog(int width,int  height)
  {
     InitializeComponent();
     Width = width;
     Height = height;
  }

}//Cls

然后在您的对话框服务更改为:

var modalWindow = new ModalCustomMessageDialog(viewModel_.Width, viewModel_.Height)
{
  // Set the content of the window as the user control
  DataContext = viewModel_,
  // Set the data context of the window as the ViewModel
  Owner = Util.AppMainWindow,
  // Set the owner of the modal window to the app window.
  WindowStartupLocation = WindowStartupLocation.CenterOwner,
  //Title = viewModel.TitleText ?? "",
  ShowInTaskbar = false,
  Content = userControl,
  SizeToContent = SizeToContent.WidthAndHeight
};

您需要定义一个视图模型可以实现的接口,它包含Width和Height。

x8diyxa7

x8diyxa77#

如果您希望使用JC a的OnContentRendered解决方案,但由于您使用的窗口是由资源字典中的样式主题化的,并且不希望在使用此样式的每个窗口中实现OnContentRendered处理程序而遇到困难,你可以为资源字典建立一个代码隐藏文件,并在其中放置处理程序。我发现这方面的一个困难是,在该样式中,您不能为RoutedEvents以外的事件设置处理程序,而OnContentRendered事件不是RoutedEvents。

解决方案

在窗口模板上为路由事件“Loaded”设置一个处理程序,如果需要,可以在其中设置OnContentRendered处理程序。
XAML:

<Style x:Key="ThemedWindow" TargetType="{x:Type Window}">
    <EventSetter Event="Loaded" Handler="Window_Loaded"/>

背后的资源字典代码:

private void Window_Loaded(Object sender, RoutedEventArgs e)
    {
        if (sender is Window window && window.SizeToContent != SizeToContent.Manual)
        {
            window.ContentRendered += Window_OnContentRendered;
        }
    }

    private void Window_OnContentRendered(Object sender, EventArgs e)
    {
        if (sender is Window window && window.SizeToContent != SizeToContent.Manual)
        {
            window.InvalidateVisual();
            window.ContentRendered -= Window_OnContentRendered;
        }
    }

相关问题