Laden...

Änderung Border.Thickness von UserControl in Verbindung mit ScrollViewer lässt Fenster einfrieren

Erstellt von SimpleTool vor 3 Jahren Letzter Beitrag vor 3 Jahren 997 Views
SimpleTool Themenstarter:in
12 Beiträge seit 2020
vor 3 Jahren
Änderung Border.Thickness von UserControl in Verbindung mit ScrollViewer lässt Fenster einfrieren

Hallo myCsharp Community,

ich habe eine Frage zu WPF und wie man so etwas umsetzen kann.

Thema:
Ich habe ein eigenes UserControl indem ein DataGrid vorhanden ist. Das letzte DataGridColumn soll dabei den restlichen Raum/Länge ausfüllen (DataGridLength.Star). Dafür musst ich im UserControl die DataGrid.Width anpassen an das UserControl (Elementbindung).
Zur Laufzeit wird dann in ein StackPanel ein/mehrere UserControls erstellt und eingefügt. (im Beispiel unten wurde das nicht gemacht, da dies für den Fehler nicht nötig ist)
Das StackPanel hingegen sitzt in einem ScrollViewer, damit ich dann, falls mein UserControl außerhalb des Fensters sich befindet, scrollen kann. Da dies nicht nur bei der "height" passieren kann sondern je nach DataGrid Columns auch in der Width, wollte ich beim ScrollViewer den HorizontalScrollBar aktiv lassen.

Soweit funktioniert das. Problem ist jedoch, wenn ich den Border.Thickness einstellen vom UserControl. Wenn ich diesen einstelle, friert mein WPF-Fenster ein und ich kann nichts mehr bedienen.
Das Problem hat was mit dem DataGrid.Width (Elementenbindung zu UserControl ActualWidth) zu tun und dem ScrollViewer HorizontalBar. Beispielsweise wenn ich den HorizontalScrollBarVisible = disable stelle, müsst das mit der Border.Thickness funktionieren.

Vielleicht kann mir hier jemand erklären wie man so etwas realisieren könnte. Habe hierfür schon mal auf "MainWindow.Loaded" Event gewartet, aber das Resultat war das Gleiche.


Zur besseren Verständnis folgend ein Beispielcode mit Erklärung.
Mein UserControl
Im User Control befindet sich mein DataGrid. Die letzte Spalte wurde dabei auf Widht=* gestellt damit dieser den kompletten Raum ausfüllt. Damit hier nicht alle Spalten "zusammengeschoben" sind, musste ich die Width vom DataGrid selber auf das UserControl (ActualWidth) binden.

<UserControl x:Class="WpfTestArea.UserControls.MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfTestArea.UserControls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800" Name="UcName">
    <StackPanel>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
            </Grid.RowDefinitions>

            <TextBlock Grid.Row="0">text</TextBlock>

            <DataGrid Name="dataGridWpf" 
                  Height="200" AutoGenerateColumns="False" 
                  Grid.Row="1"
                  Width="{Binding ElementName=UcName, Path=ActualWidth}">

                <DataGrid.Columns>
                    <DataGridTextColumn Header="eins"/>
                    <DataGridTextColumn Header="zwei" Width="*" x:Name="header2"/>
                </DataGrid.Columns>

            </DataGrid>
        </Grid>
    </StackPanel>
</UserControl>

Beim UserControl wurde beim Code-Behind kein weiterer Code bzw. Code von mir hinzugefügt. Daher werde ich den Code hier nicht auflisten.

MainWindow.xaml in dem ich das UserControl erstelle und einbinde.
Im Beispiel hier habe ich gleich in XAML mein User Control erstellt. Bei dem will ich dann z. B. BorderThickness einstellen. Das UserControl sitzt dabei in einem StackPanel, da hier dann weitere UserControls im Code-Behind hinzugefügt werden. Damit man dann scrollen kann, falls diese UserControls außerhalb des Fensters sind (weil sie so groß sind), wurde das StackPanel in den ScrollViewer gepackt.

<Window x:Class="WpfTestArea.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfTestArea"

        xmlns:uc="clr-namespace:WpfTestArea.UserControls"

        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <DockPanel>

        <ScrollViewer x:Name="myScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
            <StackPanel Name="spTest">

                <uc:MyUserControl DockPanel.Dock="Top" x:Name="ucexample"/>

            </StackPanel>
        </ScrollViewer>

    </DockPanel>
</Window>

MainWindow.cs

namespace WpfTestArea
{
    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            InitializeComponent();

            ucexample.BorderBrush = new SolidColorBrush(Colors.DarkGoldenrod);
            ucexample.Margin = new Thickness(20);

// --> Wenn ich das auskommentiere hängt sich mein WPF-Fenster auf
// Im Constructor das gemacht, da ich theoretisch hier noch weitere Usercontrols erstelle und dem StackPanel hinzufüge. Dann soll auch bei denen die border.thickness eingestellt werden.

            //ucexample.BorderThickness = new Thickness(1);

        }
     }
}

Kennt hier jemand einen Workaround wie man so etwas dann umsetzen kann?
Vielen Dank schon mal im Voraus.

Liebe Grüße

E
35 Beiträge seit 2019
vor 3 Jahren

Hallo,

ich würde spontan sagen, verschiebe die Anweisungen (außer InitializeComponent(); ) aus dem Konstruktor ins Loaded Ereignis.


        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Hier die Einstellungen vornehmen
        }

Grüße

Tobias

SimpleTool Themenstarter:in
12 Beiträge seit 2020
vor 3 Jahren

Hallo,
vielen Dank für deine Antwort Tobias.

  • ich habe das mit Window_Loaded ausprobiert -> selbe Resultat, das Fenster friert ein
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    ucexample.BorderThickness = new Thickness(2);
}
  • ich habe einen Button mal bemüht den ich drücken kann -> hier kann ich zwar das Fenster noch verschieben, jedoch ist die Bedienung des Fensters überhaupt nicht mehr akzeptabel (ruckelt)
        private void Button_Click(object sender, RoutedEventArgs e)
{
    ucexample.BorderThickness = new Thickness(2);
}

Das Problem ist also auch vorhanden, nachdem die Ladephase vorbei ist und der GUI-Thread nichts mehr zu tun hat.

Wäre noch keine Lösung X(

Liebe Grüße

4.938 Beiträge seit 2008
vor 3 Jahren

Hallo,

Beispielsweise wenn ich den HorizontalScrollBarVisible = disable stelle, müsst das mit der Border.Thickness funktionieren.

Hast du das denn mal getestet?
Evtl. mal die Reihenfolge vertauschen, d.h. erst HorizontalScrollBarVisibility = disable stellen, dann die BorderThickness und zuletzt wieder HorizontalScrollBarVisibility = auto.

SimpleTool Themenstarter:in
12 Beiträge seit 2020
vor 3 Jahren

Hi Th69,
danke für deine Antwort

Hast du das denn mal getestet?

-> ja habe ich. Hab es jetzt nochmal ausprobiert.

Wenn ich

myScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;

einstelle (sei es in XAML oder wie hier im Code-Behind) und anschließend den Border ändere wird der Border wie gewünscht gesetzt.
Wenn ich aber dann wieder

myScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;

einstelle, dann friert das Fenster ein. Auch wenn ich mit einem Button diese Eigenschaft setze. Ich kann auch nicht mehr die Größe des Fensters ändern.

Also allgemein: Ist HorizontalScrollBarVisiblility Auto, dann kann ich nicht den Border von dem UserControl setzen ohne Fehlfunktion.

Eine Möglichkeit die ich noch ausprobiert habe, ist die XAML Bindung des DataGrids zu entfernen.

                
<DataGrid Name="dataGridWpf" 
    Height="200" AutoGenerateColumns="False" 
    Grid.Row="1"
    >
    <!--Width="{Binding ElementName=UcName, Path=ActualWidth}"-->
        <DataGrid.Columns>
            <DataGridTextColumn Header="eins"/>
            <DataGridTextColumn Header="zwei" Width="*" x:Name="header2"/>
        </DataGrid.Columns>
</DataGrid>

-> dann funktioniert das Border setzen wieder, jedoch sind die DataGrid Columns zusammengeschoben und lassen sich nicht mehr bedienen. (der Width="*" funktioniert dann nicht wie ich möchte)

Liebe Grüße

4.938 Beiträge seit 2008
vor 3 Jahren

Hast du dir denn nicht sowieso mit


Width="{Binding ElementName=UcName, Path=ActualWidth}"

eine Rekursion eingebaut (denn das UserControl bestimmt seine Breite doch u.a. durch die Breite des DataGrids)?

SimpleTool Themenstarter:in
12 Beiträge seit 2020
vor 3 Jahren

Hi,

(denn das UserControl bestimmt seine Breite doch u.a. durch die Breite des DataGrids)?

Ohne HorizontalScrollBarVisible funktioniert die Erstellung vom UserControl ohne Probleme.

Ich denke mal, dass bei der Erstellung des UserControls dieses erstmal die Breite des ScrollViewers bekommt und so bekommt das DataGrid die Breite des UserControls. Die letzte Spalte kann dann den "restlichen Raum" ausfüllen.

Es wird dann wohl so eine Schleife auftreten wenn ich BorderThickness hinzufüge. Sowas wie

  • Border thickness wird gesetzt
  • UserControl Breite ändert sich dadurch
  • DatagridColumn soll restlichen raum ausfüllen
  • HorizontalScrollbar gibt aber unendlich viel Platz oder so...
    -> Leider weiß ich hier nicht was wirklich passiert und somit fällt mir kein Workaround ein.

Falls das ActualWidth das Problem auslöst, müsste ich nur wissen wie ich die letzte Spalte auf den noch aktuell restlichen Raum strechen kann. Also:
"Die letzte Spalte vom DataGrid auf den restlichen Raum zu strechen ohne das ich eine Schleife bzw. Bezug auf ActualWidth vom UserControl benötige".

Auf den ScrollViewer möchte ich eigentlich nicht verzichten, da bei mehreren Spalten vom Datagrid das UserControl und somit das DataGrid außerhalb des Fensters ist. Das würde dann alle UserControls im ScrollViewer betreffen (gemeinsam und nicht jedes einzeln, daher der ScrollViewer über dem UserControl und nicht in dem UserControl).

Gibt es hierfür eine alternative Lösung die ich verfolgen und probieren kann?

Liebe Grüße

4.938 Beiträge seit 2008
vor 3 Jahren

Kannst du nicht einfach beim DataGrid


HorizontalAlignment="Stretch"

setzen?

SimpleTool Themenstarter:in
12 Beiträge seit 2020
vor 3 Jahren

Wenn ich im UserControl das DataGrid wie folgt mache:


            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>

                <TextBlock Grid.Row="0">text</TextBlock>

                <DataGrid Name="dataGridWpf" 
                  Height="200" AutoGenerateColumns="False" 
                  Grid.Row="1"
                  
                  HorizontalAlignment="Stretch"
                  >
                    <!--Width="{Binding ElementName=UsercontrolName, Path=ActualWidth}"-->
                    <DataGrid.Columns>
                        <DataGridTextColumn Header="eins"/>

                        <DataGridTextColumn Header="zwei" Width="*" x:Name="header2"/>

                    </DataGrid.Columns>

                </DataGrid>
            </Grid>

Passiert das Gleiche wie wenn ich Strech nicht verwenden würde.

Wie das aussieht habe ich im Dateianhang.
-> letzte Spalte füllt hier nicht den restlichen Raum aus
-> ich kann die Spalten auch nicht mehr von der Width ändern mit der Maus

Funktioniert leider nicht.

EDIT:
> Im XAML-Editor von Visual-Studio zeigt er mir das aber so an wie ich es gerne hätte.
> Wenn ich jetzt HorizontalScrollBarVisibility = disabled habe würde es auch funktionieren nur dann habe ich leider kein ScrollViewer Horizontal X(

4.938 Beiträge seit 2008
vor 3 Jahren

Evtl. mußt du diese Eigenschaft auch für die Parent-Controls (StackPanel, Grid) setzen.

SimpleTool Themenstarter:in
12 Beiträge seit 2020
vor 3 Jahren

Hi again,
ich habe jetzt HorizontalAlignment="Center" immer Stück für Stück in den höheren Element eingefügt und hab dann das Resultat beobachtet (im Editor und im laufenden Programm).

Im UserControl bei Grid und dann StackPanel eingefügt -> trotzdem war der Fehler noch.

Im MainWindow beim UserControl, StackPanel, dann Scrollviewer und zuletzt DockPanel eingefügt -> trotzdem war der Fehler noch da.

Wenn ich dem StackPanel (indem das UserControl ist) eine Width gebe, also eine definierte Größe, dann funktioniert es. Jedoch verliere ich so die Option das das UserControl selber die Größe bestimmt und ich dann Scrollen kann.


<ScrollViewer x:Name="myScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" >
            <StackPanel Name="sptest" HorizontalAlignment="Stretch" 
Width="100">
                <uc:MyUserControl DockPanel.Dock="Top" x:Name="ucexample" HorizontalAlignment="Stretch"/>
            </StackPanel>
</ScrollViewer>

Irgendwie ist also am Anfang die Größe des UserControls noch nicht bekannt, daher denke ich, dass die Columns sich auf die kleine Größe rendern. Dann erst bekommt das UserControl seine Größe, aber die Columns sind ja schon gerendert... So ein Effekt in der Art müsste sein.

Evtl. müsste ich probieren erst nach der Erstellung des UserControls irgendwie erst die Columns Width zu definieren (also DataGridLength=Star).

Wär das evtl. etwas?! Wäre das was mit dem Event "Loaded"?

Liebe Grüße

5.658 Beiträge seit 2006
vor 3 Jahren

Es gibt keinen Grund, an ActualWidth/ActualHeight zu binden, um in WPF ein Layout zu erstellen. Es gibt nicht nur das StackPanel, sondern verschiedene Panels für unterschiedene Zwecke: Layout - WPF / Panel Elements and Custom Layout Behaviors

Weeks of programming can save you hours of planning

SimpleTool Themenstarter:in
12 Beiträge seit 2020
vor 3 Jahren

Hab jetzt das Binding "ActualWidth" nicht auf das UserControl-Element selbst gemacht, sondern auf das Grid-Element im UserControl. Jetzt funktioniert es wie ich es wollte.
Warum das jetzt geht und das andere nicht ist mir nur nicht klar.

5.658 Beiträge seit 2006
vor 3 Jahren

Wie gesagt:

Es gibt keinen Grund, an ActualWidth/ActualHeight zu binden, um in WPF ein Layout zu erstellen

Wenn man möchte, daß ein Grid-Element den verfügbaren Platz restlos ausfüllt, dann verwendet man ein \*, siehe dazu die Beschreibung und die Beispiele in der Doku zu GridLength.

Wenn man will, daß ein Steuerelement den verfügbaren Platz im übergeordneten Element vollständig ausnutzt, denn verwendet man HorizontalAlignment="Stretch" oder ein entsprechendes Layout-Element, bei dem das automatisch geht, siehe dazu der von mir gepostete Link weiter oben.

Alles andere ist nur Gefrickel, was dir auf Dauer viel mehr Arbeit macht, als dir mal die Beispiele in der Doku anzuschauen, und es dann richtig zu machen.

Weeks of programming can save you hours of planning