WPF自定义控件-具体如何获取正确的宽度比例

发布于 2024-11-04 09:35:51 字数 560 浏览 0 评论 0原文

我正在创建一个控件来代表员工轮班。轮班可以有不同的长度,并且没有或多次休息。

9 小时轮班、1 小时休息的样机 GUI 原型 9 小时轮班、1 小时休息的 GUI 样机

以下问题涉及原型中的蓝色条: 由于控件需要完美地调整大小,因此固定大小的方法不是一个选择。我的第一个想法是使用具有与时间跨度相同的宽度比的列的网格。因此,如果您查看上面的原型,就会发现 3 列的宽度为:240*、60*、240*。这些数字等于每个时间跨度的总分钟数。

如果我添加一个保持的依赖属性,我们将它们称为 TimeSpanItems (TSI)。每个 TSI 都有一个 TimeSpan 属性。那么是否可以将其绑定到网格及其列定义?随着 TSI 的添加,列数必须改变,并且每列必须改变其宽度比以匹配分钟数。

我是否以错误的方式思考这个问题?可行吗?或者它是一个项目控件,我需要在调整控件大小时调整其项目的大小?

目前我有不同的问题,但我还没有找到答案......并且可能有很多问题我还不知道它们是什么。任何帮助将是非常受欢迎的。

I'm in the process of creating a control to represent a employee work shift. The shift can be of different lengths and with none or more breaks.

Mockup prototype GUI of a 9 hour work shift with an 1 hour break
Mockup prototype GUI of a 9 hour work shift with an 1 hour break

The following questions refer to the blue bar in the prototype:
Since the control need to resize perfectly, then a fixed size approach is not an option. My first thought was to use a grid with columns that has the same width ratio as the time spans. So if you look at the prototype above there would be 3 columns with a width of: 240*, 60*, 240*. These numbers are equal to the total minutes of each time span.

If I add a dependency property that hold, lets call them TimeSpanItems (TSI). Each TSI has a TimeSpan property. Is it then possible to bind this to the grid and its column definitions? The number of columns must change as TSI are added and also each column must change its width ratio to match the number of minutes.

Am I thinking about this the wrong way? Is it doable? Or is it a items control that I need that resizes its items when the control is resized?

At the moment I have different questions that I yet haven't found the answer to... and probably a lot of questions that I don't know yet what they are. Any help would be most welcome.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一指流沙 2024-11-11 09:35:51

我做了一些可能对你有帮助的事情,最终的结果是这个控件:

Task Timeline

基本上列出了任务,在这些里面它显示顶部列出的“实体”的具体分配任务。

任务列表是一个 ItemsControl,如下所示:

<ItemsControl ItemsSource="{Binding Path=Tarefas}" Grid.Column="4">
    <ItemsControl.Template>
        <ControlTemplate TargetType="ItemsControl">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="30" />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <Border Grid.Row="0" Background="SlateGray" Margin="5 5 5 5">
                    <TextBlock Grid.ColumnSpan="100" Text="Cronograma" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" />
                </Border>
                <ItemsPresenter Grid.Row="1" />
            </Grid>
        </ControlTemplate>
    </ItemsControl.Template>

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid Style="{StaticResource CronogramaGrid}">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="{Binding Converter={StaticResource InicioPerc}}" />
                    <ColumnDefinition Width="{Binding Converter={StaticResource MeioPerc}}" />
                    <ColumnDefinition Width="{Binding Converter={StaticResource FimPerc}}" />
                </Grid.ColumnDefinitions>
                <Border Background="SlateGray" Grid.Column="1">
                    <Border Background="White" Margin="2 2 2 2">
                        <Grid Margin="2 2 2 2">
                            <Border Background="SlateGray" />
                            <ItemsControl ItemsSource="{Binding Path=PercentagensParticipacao}">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <Grid Height="{Binding Path=GridHeight}">
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="{Binding Path=Actual, Converter={StaticResource GridLengthStarConverter}}" />
                                                <ColumnDefinition Width="{Binding Path=Restante, Converter={StaticResource GridLengthStarConverter}}" />
                                            </Grid.ColumnDefinitions>
                                            <Border Grid.Column="0" Background="{Binding Path=Index, Converter={StaticResource IndexColorConverter}}" />
                                        </Grid>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </Grid>
                    </Border>
                </Border>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

列宽由该转换器给出。在构建此控件时,我们没有能力扩展模型,因此编写了一个转换器,如果现在完成此操作,则将扩展模型并编写 Getter。

转换器看起来像这样:

public class DataInicioPercentagemConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var tarefa = (Tarefa)value;
        var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;

        double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
        double diasTarefaInicio = tarefa.Inicio.Subtract(candidatura.DataInicio).Days;

        return new GridLength((diasTarefaInicio / totalDias * 100), GridUnitType.Star);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}

public class DataMeioPercentagemConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var tarefa = (Tarefa)value;
        var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;

        double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
        double diasTarefa = tarefa.Fim.Subtract(tarefa.Inicio).Days;

        return new GridLength((diasTarefa / totalDias * 100), GridUnitType.Star);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}

public class DataFimPercentagemConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var tarefa = (Tarefa)value;
        var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;

        double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
        double diasTarefaFim = candidatura.DataFim.Subtract(tarefa.Fim).Days;

        return new GridLength((diasTarefaFim / totalDias * 100), GridUnitType.Star);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}

I did something that could be of help to you, the final result is this control:

Task Timeline

Basically lists tasks, inside these tasks it's showing specific alocation by the "entities" that are listed on top.

The Task list is an ItemsControl that looks like this:

<ItemsControl ItemsSource="{Binding Path=Tarefas}" Grid.Column="4">
    <ItemsControl.Template>
        <ControlTemplate TargetType="ItemsControl">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="30" />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <Border Grid.Row="0" Background="SlateGray" Margin="5 5 5 5">
                    <TextBlock Grid.ColumnSpan="100" Text="Cronograma" FontWeight="Bold" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="White" />
                </Border>
                <ItemsPresenter Grid.Row="1" />
            </Grid>
        </ControlTemplate>
    </ItemsControl.Template>

    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid Style="{StaticResource CronogramaGrid}">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="{Binding Converter={StaticResource InicioPerc}}" />
                    <ColumnDefinition Width="{Binding Converter={StaticResource MeioPerc}}" />
                    <ColumnDefinition Width="{Binding Converter={StaticResource FimPerc}}" />
                </Grid.ColumnDefinitions>
                <Border Background="SlateGray" Grid.Column="1">
                    <Border Background="White" Margin="2 2 2 2">
                        <Grid Margin="2 2 2 2">
                            <Border Background="SlateGray" />
                            <ItemsControl ItemsSource="{Binding Path=PercentagensParticipacao}">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <Grid Height="{Binding Path=GridHeight}">
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="{Binding Path=Actual, Converter={StaticResource GridLengthStarConverter}}" />
                                                <ColumnDefinition Width="{Binding Path=Restante, Converter={StaticResource GridLengthStarConverter}}" />
                                            </Grid.ColumnDefinitions>
                                            <Border Grid.Column="0" Background="{Binding Path=Index, Converter={StaticResource IndexColorConverter}}" />
                                        </Grid>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </Grid>
                    </Border>
                </Border>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

The column width's are given by that converter. At the time this control was built we didn't had the ability to extend the Model, so a converter was written, if this was done now, the Model would have been extended and a Getter would have been writen.

The Converters look like this:

public class DataInicioPercentagemConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var tarefa = (Tarefa)value;
        var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;

        double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
        double diasTarefaInicio = tarefa.Inicio.Subtract(candidatura.DataInicio).Days;

        return new GridLength((diasTarefaInicio / totalDias * 100), GridUnitType.Star);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}

public class DataMeioPercentagemConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var tarefa = (Tarefa)value;
        var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;

        double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
        double diasTarefa = tarefa.Fim.Subtract(tarefa.Inicio).Days;

        return new GridLength((diasTarefa / totalDias * 100), GridUnitType.Star);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}

public class DataFimPercentagemConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var tarefa = (Tarefa)value;
        var candidatura = Indra.Injection.ServiceLocator.MefContainer.GetExportedValue<IDataManager>().Candidatura;

        double totalDias = candidatura.DataFim.Subtract(candidatura.DataInicio).Days;
        double diasTarefaFim = candidatura.DataFim.Subtract(tarefa.Fim).Days;

        return new GridLength((diasTarefaFim / totalDias * 100), GridUnitType.Star);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}
无人接听 2024-11-11 09:35:51

我来回答这个问题:可行吗?我最初的想法是我不想计算调整大小。我想要一个网格来帮我解决这个问题。因此,我将忽略所有其他问题,即这是否应该是另一种控制类型,或者这是否是“正确”的方法。

首先,我使用一个控件模板,其中 uxMainContentGrid 是添加项目时将要修改的网格。

<Style TargetType="{x:Type wpflib:RatioPresenterControl}">
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type wpflib:RatioPresenterControl}">
            <Border Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Height="{TemplateBinding Height}"
                    Width="{TemplateBinding Width}">

                <Grid x:Name="uxMainContentGrid">
                    <Grid.ColumnDefinitions>
                        <!-- Columns and ratio is set in code behind -->
                    </Grid.ColumnDefinitions>
                </Grid>
            </Border>
        </ControlTemplate>
    </Setter.Value>
</Setter>

我用来设置宽度比例的项目如下所示。

Public Class RatioItem
    Public Property Value As Double
    Public Property Brush As Brush
End Class

RatioItems 通过此依赖项属性添加,默认值为空的 RatioItems 列表。

 Public Shared ReadOnly RatioItemsProperty As DependencyProperty = _
        DependencyProperty.Register("RatioItems", GetType(IEnumerable(Of RatioItem)),
              GetType(RatioPresenterControl),
              New FrameworkPropertyMetadata(New List(Of RatioItem), AddressOf OnRatioItemsPropertyChanged))

具有以下值的依赖属性更改了回调方法。当您绑定到依赖项属性时,此回调会在应用模板之前触发。如果已设置控件模板,则此回调仅重建网格列。

Public Shared Sub OnRatioItemsPropertyChanged(sender As Object, e As DependencyPropertyChangedEventArgs)
    If (_MainContentGrid IsNot Nothing) Then
        Dim ratioItems As IEnumerable(Of RatioItem) = TryCast(e.NewValue, IEnumerable(Of RatioItem))

        ReconstructGridColumns(ratioItems, _MainContentGrid)
    End If
End Sub

以下代码在应用控件模板时检索网格。这里的目的是存储网格,以便以后在 RatioItems 属性发生更改时可以访问它。应用模板时,绑定已经生效,因此网格列已构建。

Public Overrides Sub OnApplyTemplate()
    MyBase.OnApplyTemplate()

    _MainContentGrid = TryCast(Me.Template.FindName("uxMainContentGrid", Me), Grid)

    ReconstructGridColumns(Me.RatioItems, _MainContentGrid)
End Sub

这个方法完成了所有的“构建”......

Private Shared Sub ReconstructGridColumns(ByVal ratioItems As IEnumerable(Of RatioItem), ByVal mainContentGrid As Grid)
    Dim newContent As Rectangle
    Dim columnCount As Integer = 0

    mainContentGrid.ColumnDefinitions.Clear()

    For Each item In ratioItems
        mainContentGrid.ColumnDefinitions.Add(New ColumnDefinition() With {.Width = New GridLength(item.Value, GridUnitType.Star)})
        newContent = New Rectangle() With {.Name = "item" & columnCount, .Fill = item.Brush}
        mainContentGrid.Children.Add(newContent)
        Grid.SetColumn(newContent, columnCount)
        columnCount += 1
    Next
End Sub

现在你已经完成了。这是可以做到的。现在讨论这是否是“正确”的方法......:)

I'm going to answer the question: Is it doable? My initial thought was that I didn't want the calculate the resizing. I wanted a grid to solve this for me. So I am going to ignore all the other questions regarding if this should be another control type or if this the "right" way to do it.

Here goes... first I use a control template where the uxMainContentGrid is the grid that is going to be modified when items are added.

<Style TargetType="{x:Type wpflib:RatioPresenterControl}">
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="{x:Type wpflib:RatioPresenterControl}">
            <Border Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Height="{TemplateBinding Height}"
                    Width="{TemplateBinding Width}">

                <Grid x:Name="uxMainContentGrid">
                    <Grid.ColumnDefinitions>
                        <!-- Columns and ratio is set in code behind -->
                    </Grid.ColumnDefinitions>
                </Grid>
            </Border>
        </ControlTemplate>
    </Setter.Value>
</Setter>

The items that I used to set the width ratio look like this.

Public Class RatioItem
    Public Property Value As Double
    Public Property Brush As Brush
End Class

And the RatioItems are added through this dependency property with a default value of an empty list of RatioItems.

 Public Shared ReadOnly RatioItemsProperty As DependencyProperty = _
        DependencyProperty.Register("RatioItems", GetType(IEnumerable(Of RatioItem)),
              GetType(RatioPresenterControl),
              New FrameworkPropertyMetadata(New List(Of RatioItem), AddressOf OnRatioItemsPropertyChanged))

The dependency property that has the following value changed callback method. When you bind to the dependency property, then this callback fires before the template has been applied. This callback only reconstructs the grid columns if the control template has been set.

Public Shared Sub OnRatioItemsPropertyChanged(sender As Object, e As DependencyPropertyChangedEventArgs)
    If (_MainContentGrid IsNot Nothing) Then
        Dim ratioItems As IEnumerable(Of RatioItem) = TryCast(e.NewValue, IEnumerable(Of RatioItem))

        ReconstructGridColumns(ratioItems, _MainContentGrid)
    End If
End Sub

The following code retrieves the grid when the control template is applied. The thing here is to store the grid so that it can be accessed later if the RatioItems property change. When the template is applied the binding is already in affect so the grid columns are constructed.

Public Overrides Sub OnApplyTemplate()
    MyBase.OnApplyTemplate()

    _MainContentGrid = TryCast(Me.Template.FindName("uxMainContentGrid", Me), Grid)

    ReconstructGridColumns(Me.RatioItems, _MainContentGrid)
End Sub

This method does all the "constructing"...

Private Shared Sub ReconstructGridColumns(ByVal ratioItems As IEnumerable(Of RatioItem), ByVal mainContentGrid As Grid)
    Dim newContent As Rectangle
    Dim columnCount As Integer = 0

    mainContentGrid.ColumnDefinitions.Clear()

    For Each item In ratioItems
        mainContentGrid.ColumnDefinitions.Add(New ColumnDefinition() With {.Width = New GridLength(item.Value, GridUnitType.Star)})
        newContent = New Rectangle() With {.Name = "item" & columnCount, .Fill = item.Brush}
        mainContentGrid.Children.Add(newContent)
        Grid.SetColumn(newContent, columnCount)
        columnCount += 1
    Next
End Sub

And there you have it. It can be done. Now to the discussion whether it is the "right" way to do it... :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文