C# WPF实际宽度为零

C# WPF实际宽度为零,c#,.net,wpf,layout,actualwidth,C#,.net,Wpf,Layout,Actualwidth,我有一个用户控件,它的画布高度为100,宽度为1920 加载控件时,我转到一个外部源,下载一个文本文件并将TextBlocks添加到画布。然后我想创建一个字幕滚动效果,它应该可以正常工作,除了在我将文本块添加到画布之后,我需要获取它们的宽度以便于计算,但是实际宽度属性始终为零 下面是一些代码: private readonly LinkedList<TextBlock> textBlocks = new LinkedList<TextBlock>(); public L

我有一个用户控件,它的画布高度为100,宽度为1920

加载控件时,我转到一个外部源,下载一个文本文件并将
TextBlock
s添加到画布。然后我想创建一个字幕滚动效果,它应该可以正常工作,除了在我将
文本块
添加到
画布
之后,我需要获取它们的宽度以便于计算,但是
实际宽度
属性始终为零

下面是一些代码:

private readonly LinkedList<TextBlock> textBlocks = new LinkedList<TextBlock>();

public LocalNewsControl()
{
    Loaded += LocalNewsControlLoaded;
}

private void LocalNewsControlLoaded(object sender, RoutedEventArgs e)
{
    LoadDataContext();
}

private void LoadDataContext()
{
    DataContext = new NewsItemsViewModel((exception) => LoadNewsItems());
}

private void LoadNewsItems()
{
    var viewModel = (NewsItemsViewModel)DataContext;

    NewsCanvas.Children.Clear();
    textBlocks.Clear();

    foreach (var newsViewModel in viewModel.NewsItems)
    {
        var tb = new TextBlock
        {
            Text = newsViewModel.Headline,
            FontSize = 28,
            FontWeight = FontWeights.Normal,
            Foreground = Brushes.Black
        };

        NewsCanvas.Children.Add(tb);

        Canvas.SetTop(tb, 20);
        Canvas.SetLeft(tb, -999);

        textBlocks.AddLast(tb);
    }

    Dispatcher.BeginInvoke(new Action(() =>
    {
        var node = textBlocks.First;

        while (node != null)
        {
            if (node.Previous != null)
            {
                //THIS IS WHERE ActualWidth is always ZERO
                var left = Canvas.GetLeft(node.Previous.Value) + node.Previous.Value.ActualWidth + Gap;
                Canvas.SetLeft(node.Value, left);
            }
            else
                Canvas.SetLeft(node.Value, NewsCanvas.Width + Gap);

            node = node.Next;
        }
    }));
}
private readonly LinkedList textBlocks=new LinkedList();
公共LocalNewsControl()
{
Loaded+=LocalNewsControlLoaded;
}
private void LocalNewsControlLoaded(对象发送方,RoutedEventArgs e)
{
LoadDataContext();
}
私有void LoadDataContext()
{
DataContext=newNewsItemsViewModel((异常)=>LoadNewsItems());
}
私有void LoadNewsItems()
{
var viewModel=(NewsItemsViewModel)DataContext;
newscavas.Children.Clear();
textBlocks.Clear();
foreach(viewModel.newitems中的var newsViewModel)
{
var tb=新文本块
{
Text=newsViewModel.Headline,
FontSize=28,
FontWeight=FontWeights。正常,
前景=画笔。黑色
};
newscavas.Children.Add(tb);
Canvas.SetTop(tb,20);
Canvas.SetLeft(tb,-999);
textBlocks.AddLast(tb);
}
Dispatcher.BeginInvoke(新操作(()=>
{
var节点=textBlocks.First;
while(节点!=null)
{
if(node.Previous!=null)
{
//这就是实际宽度始终为零的位置
var left=Canvas.GetLeft(node.Previous.Value)+node.Previous.Value.ActualWidth+Gap;
Canvas.SetLeft(node.Value,左);
}
其他的
SetLeft(node.Value,newscavas.Width+Gap);
node=node.Next;
}
}));
}

任何
控件的
实际高度
实际宽度
在加载
之前总是为零
测量
排列
渲染


在您的情况下,我建议使用
Loaded
SizeChanged
事件
TextBlock
对您有利。

使用
Canvas
布局
TextBlock
有什么特别的原因吗?如果没有,最好使用水平方向的
StackPanel
,它将为您处理布局数学

如果要继续执行dispatcher调用,请将优先级设置为loaded,然后它将与loaded事件同时被调用,并且您应该有一个值。
BeginInvoke上的重载也具有优先权。

您可以始终将delgate附加到
PropertyStatData/OnValueChanged
上,并且当
实际高度/实际宽度
从0更改为某个值时,调整您的滚动,
实际宽度/实际高度
至少呈现一次后将有一个值:

LocalNewsControl()
{
    var descriptor = DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(TextBlock));
    if (descriptor != null)
        descriptor.AddValueChanged(myTextBlock, ActualWidth_ValueChanged);
}

private void ActualWidth_ValueChanged(object a_sender, EventArgs a_e)
{
   //Modify you scroll things here
   ...
}

虽然有很多文本块,但我必须订阅上一个添加的加载事件,这看起来有点像黑客,你不觉得吗?我想你是对的!除非您将这些
TextBlock
控件添加到具有
水平方向的
StackPanel
中并滚动该面板。在这种情况下,您只能处理
面板的事件。控件在调用其加载的事件之前首先呈现。TextBlock不可见且未加载,因此它没有属性ActualWidth。我用Itemscontrol解决了一个类似的问题,但我对Canvas没有把握。尝试LayoutUpdated、SizeChanged等事件。我正在做一个滚动字幕效果,这意味着每个项目都必须是独立的,并且绝对定位在画布上